전략 패턴은 공통 로직을 Context라고 부르는 곳에 두고, 변하는 부분을 Strategy라는 인터페이스로 선언한다. 그리고 이 인터페이스를 다양한 구현체로 구현해서 기능을 수행한다.
Strategy를 구현한 구현체의 개수만큼 전략이 생기게 되는 것이고, 상황에 맞게 알맞은 전략을 사용한다는 개념의 패턴이라고 볼 수 있다.
예시 코드를 통해서 좀 더 자세히 알아보도록 하자.
💡 템플릿 메서드 패턴이 "상속"을 통해서 중복을 제거했다면, 전략 패턴은 역할의 "위임"을 통해서 중복을 제거한다.
이전에 작성했던 템플릿 메서드 패턴 글과 마찬가지로, 서로 다른 2개의 기능이 존재하고, 이 기능들이 실행되는 시간을 측정해야되는 상황에 처했다고 가정해보자.
public interface Strategy {
void run();
}
먼저, 아주 간단하게 전략 인터페이스를 하나 만들어주자.
위 인터페이스를 상속해서 서로 다른 2개의 구현 클래스를 만들어주자.
public class Strategy1 implements Strategy{
@Override
public void run() {
System.out.println("전략1, 로직 실행");
}
}
public class Strategy2 implements Strategy{
@Override
public void run() {
System.out.println("전략2, 로직 실행");
}
}
공통된 로직이 담길 Context 역할의 클래스를 만들어주자.
@Slf4j
@AllArgsConstructor
public class Context {
private Strategy strategy;
public void execute() {
long startMs = System.currentTimeMillis();
strategy.run(); // ⭐
long endMs = System.currentTimeMillis();
log.info("SpendMs={}", endMs - startMs);
}
}
상황에 맞는 Strategy 구현 클래스를 생성자의 매개변수로 넘기도록 구성하였다.
import hello.springaop.pattern.strategy.code.Context;
import hello.springaop.pattern.strategy.code.Strategy1;
import hello.springaop.pattern.strategy.code.Strategy2;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
@Slf4j
public class StrategyTest {
@Test
@DisplayName("strategyTest")
public void strategyTest() throws Exception {
Strategy1 strategy1 = new Strategy1();
Strategy2 strategy2 = new Strategy2();
Context context1 = new Context(strategy1);
Context context2 = new Context(strategy2);
context1.execute();
context2.execute();
}
}
위와 같이 테스트 코드를 작성해서 구동해보면 아래와 같이 예상한대로 기능별 소요 시간이 잘 찍히는 것을 확인할 수 있다.

위의 과정들을 통해서 전략 패턴의 예시와 동작 방식을 확인해보았다.
이러한 전략 패턴은 스프링에서 매우 많이 사용되는 패턴이다.
바로 스프링에서 가장 중요한 개념 중에 하나인 DI(Dependency injection)이다.
위의 예시에서도 확인할 수 있듯이, Strategy를 인터페이스화 함으로써 Context와 Strategy는 완변히 독립적인 구조를 갖고 있다. 스프링 DI에서도 이 특징을 활용하고 있는 것이고, 이러한 특징 덕분에 개발자는 객체 지향적인 개발이 가능해진다. 🤗