전략(Strategy) 패턴
1. 의도
객체가 사용하는 알고리즘을 추상화하고 선택적으로 사용할 수 있게 해준다.
2. 활용
공통성이 없는 여러 객체가 비슷한 알고리즘을 사용할 때, 함수를 객체로 만들어 사용하면 유용하다. 이에 대한 함수를 추상화하여 클래스로 정의하고 인스턴스를 만들 때 알고리즘을 선택할 수 있게 한다.
- UI 화면에 띄울 문자를 파싱하여 자동으로 라인을 분리하는 객체
Composition 객체는 화면에 띄울 문자를 모두 가지고 있으며 라인 분리 알고리즘을 사용할 주체이다. Composition 객체는 직접 알고리즘을 구현하지 않으며, Compositor라는 클래스에 알고리즘을 구현하여 사용한다. Compositor 객체는 UI 화면에 띄울 문자와 라인 분리를 위한 문자 넓이, 라인 1줄 당 문자 갯수, 문자 간 간격, 문자 종류 등의 정보를 받아 어느 문자에서 라인을 분리할 것인지 인덱스를 반환한다.
Composition 객체는 Compositor의 라인 분리 알고리즘으로 가지고있는 텍스트의 끝에 도달할 때까지 매번 반환된 인덱스를 통해 라인을 구분할 수 있다.
추상 클래스 Compositor의 구체 Concrete 클래스는 다음이 예시가 될 수 있다.
- SimpleCompositor: 단순히 한 번에 한 라인 씩 분리하는 간단한 전략을 구현한다.
- TeXCompositor: TeX 문법의 "\linesplit"과 같은 명령어를 만났을 때, 라인을 분리하는 전략을 구현한다.
- ArrayCompositor: 클래스는 각 라인마다 고정된 문자수를 가지도록 라인을 분리하는 전략을 구현한다.
다음 상황에서 전략 패턴을 사용할 수 있다.
- 많은 알고리즘을 공통적으로 추상화하여 정의할 수 있는 경우
- 주어진 정보에 따라 알고리즘의 변형이 필요할 때
- 알고리즘의 처리 과정 중 사용자가 몰라야하는 자료 구조가 포함되어 있는 경우
- 조건문 분기에 따라 알고리즘이 선택되는데, 조건문이 많을 경우
3. 참여자
- Strategy(Compositor): 제공하는 모든 알고리즘에 대한 공통의 연산들을 인터페이스로 정의한다.
- ConcreteStrategy(SimpleCompositor, TeXCompositor, ArrayCompositor): Strategy 객체에 대한 참조자를 관리하고 Strategy 서브클래스의 인스턴스를 갖고 있음으로써 구체화한다.
- Context(Composition): ConcreteStrategy 객체를 통해 구성. 어떤 전략을 사용할 것 인지 Strategy 객체에 대한 참조자를 관리하고 Strategy 객체가 알고리즘 처리에 필요한 정보를 접근하기 위한 Get()과 같은 인터페이스를 정의한다.
4. 협력 방법
- Context 클래스는 알고리즘에 해당하는 연산이 호출되면, 알고리즘 처리에 필요한 모든 데이터를 Strategy 클래스로 보낸다.
- Context 클래스는 사용자쪽에서 온 요청을 전략 객체로 전달한다.
5. 결과
1) 동일 계열의 관련 알고리즘 군이 생긴다.
- 다양한 알고리즘도 다양한 클래스 계통으로 분리하여 묶을 수 있으며 이는 동일 계열로 관리할 수 있다.
2) 서브클래싱을 사용하지 않는다.
- Context를 상속하는 서브 클래싱은 추후 알고리즘을 수정해야 할때 관련 서브 클래스를 모두 수정해야하고 관리가 힘들다. 복잡한 알고리즘을 추상화하여 구현 부를 따로 두는 것이 좋다.
3) 조건문을 피한다.
- 필요한 알고리즘이 조건문에 따라 선택된다면 조건 분기가 많아질 수 록 코드 길이가 길어진다. 정적으로 알고리즘을 선택할 수 있다면, 조건문 없이 전략 패턴으로 구현 가능하다.
4) 구현 선택이 가능해진다.
- 여러 알고리즘을 선택하여 사용한다.
5) 사용자는 서로 다른 전략을 알아야한다.
- 어떤 전략을 사용할지 선택하기 위해 Composition 주체를 인스턴스화 하는 과정에서 전략에 관한 Compositor를 선택하여 생성해야한다.
6) Strategy 객체와 Context 객체간의 의사소통 오버헤드가 있다.
- Strategy 객체가 알고리즘 처리에 모든 정보를 가지고 있지 않다면 Context 참조자와 같은 포인터를 통해 정보를 얻어야하는데, 이와 같은 접근에 대한 오버헤드가 있다.
7) 객체 수가 증가한다.
- Strategy 객체가 많은 Context 객체에 의해 필요하다면 많은 인스턴스가 생길 수 있다. 이 인스턴스들을 공유할 수 있는 방법을 모색해야한다.
6. 구현
1) Strategy 및 Context 인터페이스를 정의한다.
- Strategy 및 Context 두 인터페이스는 서로 의사소통을 위한 접근 방법을 구현하고 어떤 정보를 전달할 것인지 결정해야한다.
- 알고리즘 실제 동작 호출시 함수 파리미터로 전달하거나 객체가 생성할 때 Strategy 객체가 가지고 있을 수 도 있다.
- Strategy 객체가 가지고 있는 정보가 많다면, 클래스와 결합도가 높아지게 된다.
2) 전략을 템플릿 매개변수로 사용한다.
- 템플릿 기능을 통해 Context 클래스의 템플릿 매개변수로 Strategy를 정의한다면, Strategy 객체는 컴파일 타임에 결정될 수 있어야 한다.
- Context * context = Context<ConcreteStrategy>();
- Strategy 인터페이스를 정의하는 추상 클래스를 정의할 필요가 없어진다.
3) Strategy 객체에 선택성을 부여한다.
- 굳이 Strategy 객체가 필요없다면 선택하지 않아도 된다.
예제 코드
처음 예시에서 언급한 텍스트 문자열을 표시하기 위해 문자열을 가지고 있는 Composition 클래스와 여러 구체 Compositor 클래스를 정의해본다.
Composition.h
실제 문자열을 가지고 있는 주체로 텍스트를 표시하기 위한 정보를 가지고 있다. 만약 Compositor에 자신의 인스턴스에 대한 this 참조자 형태로 전달할 경우, Get 메서드가 필요할 수도 있다.
class Composition
{
public:
Composition(class Compositor*);
void Repair();
private:
class Compositor* _compositor;
class Component* _components; // 구성요소 리스트
int _componentCount; // 구성요소 수
int _lineWidth; // 라인의 넓이
int* _lineBreaks; // 줄 분리자의 위치
int _lineCount; // 라인 수
};
Compositor.h
줄 분리를 위한 알고리즘에 관련된 인터페이스를 정의하도록 한다.
// 라인 분리 알고리즘에 대한 추상 클래스
class Compositor
{
public:
// 실제 라인을 분리하기 위해 필요한 정보를 전달한다.
virtual int Compose(Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]) = 0;
protected:
Compositor();
};
SimpleCompositor.h / TexCompositor.h / ArrayCompositor.h
줄 분리를 위한 여러 구체 Compositor 클래스이다.
#include "Compositor.h"
class SimpleCompositor : public Compositor
{
public:
SimpleCompositor();
virtual int Compose(Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]);
// ...
};
#include "Compositor.h"
class TexCompositor : public Compositor
{
public:
TexCompositor();
virtual int Compose(Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]);
};
#include "Compositor.h"
class ArrayCompositor : public Compositor
{
private:
int _interval;
public:
ArrayCompositor(int interval);
virtual int Compose(Coord natural[], Coord stretch[], Coord shrink[],
int componentCount, int lineWidth, int breaks[]);
// ...
};
사용자 코드에서는 다음과 같이 전략 객체를 선택하여 Composition 객체 생성시 파리미터로 전달해준다.
#include "Composition.h"
#include "TexCompositor.h"
#include "SimpleCompositor.h"
#include "ArrayCompositor.h"
int main()
{
Composition* quick = new Composition(new SimpleCompositor);
Composition* slick = new Composition(new TexCompositor);
Composition* iconic = new Composition(new ArrayCompositor(100));
return 0;
}
'디자인 패턴 > GoF' 카테고리의 다른 글
[디자인 패턴] 행동 패턴 (8) 반복자 Iterator (0) | 2021.03.24 |
---|---|
[디자인 패턴] 행동 패턴 (7) 중재자 Mediator (0) | 2021.03.23 |
[디자인 패턴] 행동 패턴 (5) 감시자 Observer (0) | 2021.02.04 |
[디자인 패턴] 행동 패턴 (4) 상태 State (0) | 2021.02.03 |
[디자인 패턴] 행동 패턴 (3) 해석자 Interpreter (0) | 2021.02.02 |