전략 패턴은 알고리즘들의 패밀리를 정의하고, 각 패밀리를 별도의 클래스에 넣은 후 그들의 객체들을 상호교환할 수 있도록 하는 행동 디자인 패턴입니다.
한 코드에서 다르게 동작해야 하는 부분들이 존재할 때, 그 부분들을 따로 빼는 것
Ex)
기존 Navigator 클래스는
public class Navigator {
public Route roadRoute() {
// ... 도로 루트 로직
return route;
}
}
와 같은 형태로 이루어져 있었는데, 어플리케이션이 확장되면서 대중교통, 걷기 루트가 추가됨.
public class Navigator {
public Route roadRoute() {
// ... 도로 루트 로직
return route;
}
public Route walkingRoute() {
// ... 걷기 루트 로직
return route;
}
public Route publicTransportRoute() {
// ... 대중교통 루트 로직
return route;
}
}
문제점:
- 새 경로 구축 알고리즘을 추가할 때마다 내비게이터의 메인 클래스의 크기가 늘어남.
- 알고리즘 중 하나를 변경하면 전체 클래스에 영향이 미쳐 이미 작동하는 코드에서 오류가 발생할 가능성이 높아짐.
- 새로운 기능을 구현하려면 거대한 동일 클래스를 변경해야 했는데 거대한 동일 클래스를 변경해야 하기 때문에 병합 충돌이 생김.
해결책:
전략 패턴은 특정 작업을 다양한 방식으로 수행하는 클래스를 선택한 후 모든 알고리즘을 전략들(strategies)이라는 별도의 클래스들로 추출.
콘텍스트(context)라는 원래 클래스에는 전략 중 하나에 대한 참조를 저장하기 위한 필드가 있어야 합니다. 콘텍스트는 작업을 자체적으로 실행하는 대신 연결된 전략 객체에 위임
Strategy
public interface Strategy {
void 변하는_부분();
}
public class Strategy1 implements Strategy {
@Override
public void 변하는_부분() {
System.out.println("변하는 부분 - Strategy 1");
}
}
public class Strategy2 implements Strategy {
@Override
public void 변하는_부분() {
System.out.println("변하는 부분 - Strategy 2");
}
}
Context
public class Context {
private Strategy strategy;
public StrategyPattern(Strategy strategy) {
this.strategy = strategy;
}
public void 변하지_않는_부분() {
System.out.println("변하지 않는 부분 시작");
strategy.변하는_부분();
System.out.println("변하지 않는 부분 종료");
}
}
Context는 변하지 않는 로직을 가지고 있는 템플릿 역할을 하는 코드. 이 코드 안에서 Strategy를 통해 일부 전략이 변경된다. Strategy 인터페이스에만 의존하기 때문에 구현체를 주입하기만 하면 된다.
새로운 전략이 필요한 경우, 새로운 Strategy 구현체를 구현하기만 하면 됨.
사용 예제
public class StrategyPattern {
void test() {
Strategy strategy1 = new Strategy1();
Strategy strategy2 = new Strategy2();
Context context1 = new Context(strategy1):
context1.변하지_않는_부분();
Context context2 = new Context(strategy2):
context2.변하지_않는_부분();
}
}
// 결과
// 변하지 않는 부분 시작
// 변하는 부분 - Strategy 1
// 변하지 않는 부분 종료
// 변하지 않는 부분 시작
// 변하는 부분 - Strategy 2
// 변하지 않는 부분 종료
public class TemplateMethodPattern {
void test() {
Context context1 = new Context(() -> System.out.println("변하는 부분 - Strategy 1")):
context1.변하지_않는_부분();
Context context1 = new Context(() -> System.out.println("변하는 부분 - Strategy 2")):
context2.변하지_않는_부분();
}
}
익명 클래스 및 람다를 이용해서 더 깔끔하게 사용할 수 있음. (인터페이스에 메소드가 1개만 있을 경우 람다 사용 가능)
스프링 의존관계 주입에서도 이 전략 패턴을 사용한다.
장점:
바뀌는 부분들이 인터페이스에만 의존하고 있기 때문에, Context가 바뀌더라도 전략들에는 영향이 가지 않음
단점:
Context와 Strategy를 한번 조립하고 나면 전략을 변경하기가 어렵다.
-> Setter를 제공해서 strategy를 넘겨줄 수도 있지만, 싱글턴 인스턴스의 경우 동시성 이슈 등 고려할 점이 많다.
해당 단점을 보완하기 위해, 생성 시점이 아닌 메소드 실행 시점에 Strategy를 선택하도록 하는 방식을 사용할 수 있다.
public class Context {
public void 변하지_않는_부분(Strategy strategy) {
System.out.println("변하지 않는 부분 시작");
strategy.변하는_부분();
System.out.println("변하지 않는 부분 종료");
}
}
public class StrategyPattern {
void test() {
Context context = new Context():
context1.변하지_않는_부분(new Strategy1());
context1.변하지_않는_부분(new Strategy2());
}
// Lambda
void test() {
Context context = new Context():
context1.변하지_않는_부분(() -> System.out.println("바뀌는 부분 - Strategy 1"));
context1.변하지_않는_부분(() -> System.out.println("바뀌는 부분 - Strategy 2"));
}
}
단점으로는, 실행할 때마다 전략을 계속 지정해주어야 한다는 것이다.
'Computer Science > 디자인 패턴' 카테고리의 다른 글
Template Method Pattern (템플릿 메소드 패턴) (1) | 2025.04.22 |
---|