Spring 핵심원리 고급편 - Strategy 패턴

목차

Strategy 패턴

Strategy 패턴 은 변하지 않는 부분을 Context 에 두고 변하는 부분을 Strategy 라는 Interface 를 만들고 해당 인터페이스를 구현해 코드 중복성을 해결한다.

Strategy 패턴 코드 중복 문제를 해결하기 위한 디자인 패턴 중 하나로 Context 와 Strategy 으로 구성됩니다. 공통 로직은 Context 클래스로 만들어 관리하고 서로 다른 로직들을 Strategy 인터페이스를 구현하는 방식으로 코드 중복 문제를 해결합니다.

Strategy 패턴 에서 Context 클래스는 내부에 Strategy 클래스를 가지고 있습니다. 이 구조는 Context 객체가 실행될 때, 내부에 저장된 Strategy 객체를 활용하여 작업을 수행하게 됩니다. 따라서 Context 객체는 실행 시 Strategy 객체에 의존하게 됩니다.

Strategy 클래스들은 서로 다른 로직을 가지고 있지만 동일한 인터페이스로 구현됩니다. 결과적으로 Context 클래스는 동일한 메소드 호출을 통해 다양한 Strategy 객체를 실행할 수 있습니다.

Template Method 패턴과의 차이점은 공통적인 부분을 추상 클래스에서 관리하고 상속을 통해 변하는 부분을 구현해 코드 중복성을 해결 했습니다. Startegy Pattern 은 상속이 아니라 위임 으로 문제를 해결한다.

Strategy 인터페이스 - 변하는 로직을 관리

public interface Strategy {
void call();
}

Strategy 구현체

StrategyLogic1 클래스와 StrategyLogic2 클래스는 동일한 Strategy 인터페이스를 이용해 구현합니다.

@Slf4j
public class StrategyLogic1 implements Strategy{
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
}
@Slf4j
public class StrategyLogic2 implements Strategy{
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
}

Context - 변하지 않는 공통 로직을 관리

전략 패턴의 핵심은 ContextStrategy 인터페이스에 의존한다는 것이다.

ContextV1 객체는 내부에 Strategy 객체를 갖고 있는 모습을 확인할 수 있습니다. ContextV1 객체 초기화시 다양한 Strategy 객체를 전달함으로써 공통 로직을 수행하면서 다양한 로직을 수행할 수 있습니다. 결과적으로 공통적인 코드를 분리함으로써 코드 중복을 줄이고 재사용성을 높일 수 있었습니다.

Spring 에서 사용하는 의존성 주입 방식 이 바로 Strategy Pattern 입니다. 공통적으로 수행되는 Spring 프레임워크에 특정 로직을 수행하도록 구현된 객체를 전달함으로써 공통된 Spring 로직과 개인이 구현한 로직을 수행할 수 있습니다.

큰 뼈대를 갖는 Context 는 수정하지 않으면서 특정로직을 수행하는 Stategy 만 생성해 실행함으로써 객체 지향 원칙 중 하나인 개방-폐쇄 원칙(OCP) 을 따르는 것을 확인할 수 있습니다.

@Slf4j
public class ContextV1 {

// Strategy 인터페이스에 의존
private Strategy strategy;

public ContextV1 (Strategy strategy){
this.strategy = strategy;
}

public void execute(){
long startTime = System.currentTimeMillis();

// 비즈니스 로직 실행
strategy.call(); // Strategy 객체를 실행
// 비즈니스 로직 종료

long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime = {}", resultTime);
}
}

Strategy Pattern 실행 및 결과

공통 로직을 수행하는 ContextV1 객체에 StrategyLogic1 객체와 StrategyLogic2 객체를 주입해 줌으로써 서로다른 로직을 수행하게 됩니다.

StrategyLogic1 객체와 StrategyLogic2 객체는 같은 Strategy 인터페이스를 이용해 구현되었기 때문에 ContextV1 객체는 동일한 메소드를 이용해 주입된 객체를 실행시킬 수 있습니다.

@Test
void strategyV1(){
StrategyLogic1 strategyLogic1 = new StrategyLogic1();
// StrategyLogic1 객체를 ContextV1 에 주입해준다.
ContextV1 context1 = new ContextV1(strategyLogic1);
context1.execute();

StrategyLogic2 strategyLogic2 = new StrategyLogic2();
// StrategyLogic2 객체를 ContextV1 에 주입해준다.
ContextV1 context2 = new ContextV1(strategyLogic2);
context2.execute();
}

익명 내부 클래스 사용

@Test
void strategyV2(){
Strategy strategyLogic1 = new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
};

ContextV1 contextV1 = new ContextV1(strategyLogic1);
log.info("strategyLogic1 = {}", contextV1.getClass());
contextV1.execute();

Strategy strategyLogic2 = new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
};

ContextV1 contextV2 = new ContextV1(strategyLogic2);
log.info("strategyLogic2 = {}", contextV2.getClass());
contextV2.execute();
}

익명 내부 클래스 & 람다 이용하기

@Test
void strategyV3() {
ContextV1 contextV1 = new ContextV1(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
});

log.info("strategyLogic1 = {}", contextV1.getClass());
contextV1.execute();

ContextV1 contextV2 = new ContextV1(() -> log.info("비즈니스 로직 2 실행"));
log.info("strategyLogic2 = {}", contextV2.getClass());
contextV2.execute();
}

파라미터로 전달 받는 방식

Strategy 를 필드로 갖지 않고, 파라미터로 전달 받는다.

@Slf4j
public class ContextV2 {

// private Strategy strategy;

// Strategy 를 필드로 갖지 않고, 파라미터로 전달 받는다.
public void execute(Strategy strategy){
long startTime = System.currentTimeMillis();
// 비즈니스 로직 실행
strategy.call();
// 비즈니스 로직 종료
long endTime = System.currentTimeMillis();
long resultTime = endTime - startTime;
log.info("resultTime = {}", resultTime);
}
}

Context 를 실행할 때마다 파라미터를 전달 받는다.

@Test
void strategyV1(){
ContextV2 context = new ContextV2();
context.execute(new StrategyLogic1());
context.execute(new StrategyLogic2());
}
@Test
void strategyV2(){
ContextV2 context = new ContextV2();
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 1 실행");
}
});
context.execute(new Strategy() {
@Override
public void call() {
log.info("비즈니스 로직 2 실행");
}
});
}
@Test
void strategyV3(){
ContextV2 context = new ContextV2();
context.execute(() -> log.info("비즈니스 로직 1 실행"));
context.execute(() -> log.info("비즈니스 로직 2 실행"));
}
Share