알고리즘들의 패밀리를 정의해 각각 별도의 클래스에 집어넣어 객체들끼리 상호 교환 가능하게 만드는 행동 패턴
내비 앱 가정
전략 패턴 - 특정 작업을 다양한 방법으로 하는 클래스에서 알고리즘들을 전략이라는 별도 클래스로 추출
원본 클래스(컨텍스트) - 전략 중 하나에 대한 참조 저장, 컨텍스트는 작업을 연결된 전략 객체에 위임
컨텍스트는 알고리즘을 선택하는 데 책임 없음, 클라이언트가 컨텍스트에 원하는 전략 전달
→ 컨텍스트는 concrete 전략에 의존하지 않아 기존 코드에 영향을 주지 않고 새 알고리즘 추가 가능
내비 앱 → 각 경로 알고리즘은 각각 단일 buildRoute
메서드를 가지는 클래스로 추출될 수 있음
1. 컨텍스트 - concrete 전략 중 하나에 대한 참조를 가지며, 전략 인터페이스를 통해서만 해당 객체와 소통함
2. 전략 - 모든 concrete 전략들에게 공통된 인터페이스
- 컨텍스트가 전략을 실행하는 데 사용하는 메서드 선언
3. concrete 전략 - 컨텍스트가 사용하는 알고리즘의 다양한 변형 구현
4. 컨텍스트는 알고리즘을 실행해야 할 때마다 연결된 전략 객체의 실행 메서드 호출
- 컨텍스트는 무슨 전략인지, 어떻게 알고리즘이 실행되는지 모름
5. 클라이언트 - 특정 전략 객체를 생성해 객체에 전달
- 컨텍스트는 클라이언트가 컨텍스트와 연관된 전략을 런타임에 변경할 수 있게 해주는 setter 노출
- 특정 서브태스크를 다른 방식으로 수행할 수 있는 서브 객체를 연동해
객체의 행동을 런타임에 간접적으로 변경할 수 있게 해줌
- 변화하는 행동을 별도 클래스 계층으로 추출하고 기존 클래스들을 하나로 합쳐 코드 중복 감소
- 다양한 알고리즘들의 코드, 내부 데이터, 의존성을 나머지 코드로부터 분리 가능
- 클라이언트들은 알고리즘을 실행하고 런타임에 변경하는 간단한 인터페이스 획득
- 모든 알고리즘들을 같은 인터페이스를 구현하는 별도 클래스들로 추출
- 기존 객체는 알고리즘의 모든 변형을 구현하는 대신 전략 객체 중 하나에 실행을 위임
1. 컨텍스트 클래스에서 자주 변화하는 알고리즘 식별
- 런타임에 동일한 알고리즘의 변형을 선택하고 실행하는 거대한 조건문일수도 있음
2. 알고리즘의 모든 변형들에 공통되는 전략 인터페이스 선언
3. 모든 알고리즘을 각각 전략 인터페이스를 구현하는 별도 클래스로 추출
4. 컨텍스트 클래스에 전략 객체에 대한 참조를 저장하는 필드 추가
- 해당 필드 값을 변경하는 setter 제공
- 컨텍스트는 전략 인터페이스를 통해서만 전략 객체와 소통해야 함
- 컨텍스트는 전략이 자신의 데이터에 접근할 수 있게 해주는 인터페이스를 정의할 수도 있음
5. 컨텍스트의 클라이언트들은 원하는 주요 작업 수행 방식에 맞게 컨텍스트와 전략을 매치시켜줘야 함
- 객체 내부에서 사용되는 알고리즘을 런타임에 변경 가능
- 알고리즘의 구현 세부 사항을 알고리즘을 사용하는 코드로부터 격리 가능
- 상속을 합성으로 변경 가능
- OCP - 컨텍스트 변경 없이 새 전략 추가 가능
- 거의 바뀌지 않는 몇 개의 알고리즘만 있는 경우, 굳이 패턴을 적용해 프로그램을 과도하게 복잡하게 만들 필요 없음
- 클라이언트가 적절한 전략을 선택하기 위해 전략 간 차이점에 대해 인지하고 있어야 함
- 대부분 현대 프로그래밍 언어들은 익명 함수 집합 안에 알고리즘의 다양한 버전을 구현할 수 있게 해주는 함수 타입 지원
→ 추가 클래스, 인터페이스 없이 전략 패턴과 똑같이 사용할 수 있음
- 브리지, 파사드, 전략, 어댑터 - 다른 객체에 작업을 위임하는 합성 기반 패턴이라는 점에서 유사하지만
모두 다른 문제를 해결함
- 커맨드 패턴과 전략 패턴은 객체를 특정 작업으로 매개변수화할 수 있다는 점에서 비슷해 보이지만 다른 의도를 가짐
커맨드 - 어떤 작업이든 객체로 변환할 수 있음, 작업의 매개변수는 객체의 필드가 됨,
해당 변환은 작업 지연 실행, 작업을 대기열에 추가, 커맨드들의 history 저장,
커맨드들을 원격 서비스에 전달하는 등 기능을 가능하게 함
전략 - 주로 같은 일을 하는 여러 방법들을 설명해, 단일 컨텍스트 클래스 내부에서
해당 알고리즘들을 바꿔가며 사용할 수 있게 함
- 데코레이터 패턴은 객체의 피부를 변경할 수 있고, 전략 패턴은 객체의 내장을 변경할 수 있음
- 템플릿 메서드 패턴 - 상속 기반, 알고리즘의 부분들을 서브클래스에서 확장해 변경,
클래스 레벨에서 동작해 정적임
전략 패턴 - 합성 기반, 객체 행동의 부분들을 다른 전략을 제공해줌으로써 변경,
객체 레벨에서 동작해 런타임에 행동들을 변경
- 상태 패턴 - 전략 패턴의 확장으로 간주할 수 있음, 두 패턴 모두 합성 기반
- 전략 패턴 - 객체들은 완전히 독립적이고 서로 인식 못 함
- 상태 패턴 - concrete 상태 간 의존성에 제한을 두지 않고 스스로 컨텍스트의 상태를 변경할 수 있게 함
/**
* The Context defines the interface of interest to clients.
*/
class Context {
/**
* @type {Strategy} The Context maintains a reference to one of the Strategy
* objects. The Context does not know the concrete class of a strategy. It
* should work with all strategies via the Strategy interface.
*/
private strategy: Strategy;
/**
* Usually, the Context accepts a strategy through the constructor, but also
* provides a setter to change it at runtime.
*/
constructor(strategy: Strategy) {
this.strategy = strategy;
}
/**
* Usually, the Context allows replacing a Strategy object at runtime.
*/
public setStrategy(strategy: Strategy) {
this.strategy = strategy;
}
/**
* The Context delegates some work to the Strategy object instead of
* implementing multiple versions of the algorithm on its own.
*/
public doSomeBusinessLogic(): void {
// ...
console.log('Context: Sorting data using the strategy (not sure how it\'ll do it)');
const result = this.strategy.doAlgorithm(['a', 'b', 'c', 'd', 'e']);
console.log(result.join(','));
// ...
}
}
/**
* The Strategy interface declares operations common to all supported versions
* of some algorithm.
*
* The Context uses this interface to call the algorithm defined by Concrete
* Strategies.
*/
interface Strategy {
doAlgorithm(data: string[]): string[];
}
/**
* Concrete Strategies implement the algorithm while following the base Strategy
* interface. The interface makes them interchangeable in the Context.
*/
class ConcreteStrategyA implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.sort();
}
}
class ConcreteStrategyB implements Strategy {
public doAlgorithm(data: string[]): string[] {
return data.reverse();
}
}
/**
* The client code picks a concrete strategy and passes it to the context. The
* client should be aware of the differences between strategies in order to make
* the right choice.
*/
const context = new Context(new ConcreteStrategyA());
console.log('Client: Strategy is set to normal sorting.');
context.doSomeBusinessLogic();
console.log('');
console.log('Client: Strategy is set to reverse sorting.');
context.setStrategy(new ConcreteStrategyB());
context.doSomeBusinessLogic();
// Output.txt
Client: Strategy is set to normal sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
a,b,c,d,e
Client: Strategy is set to reverse sorting.
Context: Sorting data using the strategy (not sure how it'll do it)
e,d,c,b,a
참고 자료: Refactoring.guru