3 분 소요

Strategy는 알고리즘이나 기능등의 전략을 캡슐화하고 런타임에 변경할 수 있게 만들어 줍니다. 외부에서 동작을 결정하고 변경할 수 있어 확장성이 향상됩니다.

설명

보통은 어떠한 기능을 실행할 때 함수를 호출하므로, 컴파일 타임에 실행이 결정됩니다.

1
2
3
Rectangle rectangle{0, 0, 10, 20};

rectangle.SaveXml(); // Xml로 저장합니다.

만약 사용자의 선택에 따라 런타임에 저장하는 방식을 변경하고 싶다면, 다음과 같이 if()문을 사용할 수 있습니다.

1
2
3
4
5
6
7
8
Rectangle rectangle{0, 0, 10, 20};

if (사용자가 Xml 저장을 선택했다면) {
    rectangle.SaveXml(); // Xml로 저장합니다.
}
else if (사용자가 Json 저장을 선택했다면) {
    rectangle.SaveJson();
}

하지만, 상기의 방식은 컴파일 타임에 SaveXml()SaveJson()을 모두 구현해야 하므로 Rectangle 개체가 덩치가 커지는 문제가 있습니다. 이렇게 커지다 보면 블롭이 될 수도 있죠.

이러한 경우 Strategy 패턴을 이용하여 런타임에 선택적으로 저장 전략을 변경할 수 있습니다.

다음 그림은 Strategy의 일반적인 구조입니다.

ConcreteStrategy1ConcreteStrategy2는 주어진 전략을 구현합니다. Context는 전략이 바뀔 때마다 ConcreteStrategy1이나 ConcreteStrategy2를 참조하게 되며, ContextInterface() 호출시 설정된 전략을 실행하게 됩니다.

Strategy

상기 Rectangle의 경우는 다음과 같이 구성할 수 있습니다. RectangleSave()함수는 어떤 전략을 사용하느냐에 따라 XmlWriterJsonWriter를 사용할 수 있습니다.

Strategy

항목 내용
Context 기능이나 알고리즘 실행시 Startegy를 사용합니다.
Strategy 기능이나 알고리즘의 공통 인터페이스를 정의합니다.
ConcreteStrategy1, ConcreteStrategy2 주어진 전략에 따라 기능이나 알고리즘을 구현합니다.

특징

외부에서 특정 전략을 주입(의존성 주입)하여 런타임에 유연하게 알고리즘이나 동작을 변경할 수 있습니다.(의존성 역전 원칙 참고)

Strategy 패턴State 패턴과 구조가 동일합니다. 알고리즘이나 기능을 응집하느냐, 상태를 응집하느냐에 따라 Strategy 패턴이냐 State 패턴이냐를 구분할 수 있습니다.

예제

다음은 Rectangle개체를 저장하는 예입니다. Save()시 어떤 전략을 설정했느냐에 따라 XmlWriterJsonWriter가 실행됩니다.

  1. #1 : IWriterStrategy입니다. WriteRectangle() 함수로 Rectangle 정보를 저장합니다.
  2. #2 : XmlWriter, JsonWriterIWriter를 구체화 합니다. 각각 Xml 또는 Json으로 저장합니다.
  3. #3 : RectangleSave()시 설정된 IWriter에 따라 다른 전략으로 저장합니다.
  4. #4 : SetWriter()를 이용하여 런타임에 전략을 변경합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
// ----
// #1. WriteRectangle() 함수로 Rectangle 정보를 저장합니다.
// ----
class IWriter {
protected:
    IWriter() = default; // 인터페이스여서 상속해서만 사용하도록 protected
    ~IWriter() = default; // 인터페이스여서 다형 소멸을 하지 않으므로 protected non-virtual
private:
    IWriter(const IWriter&) = delete;
    IWriter(IWriter&&) = delete;
    IWriter& operator =(const IWriter&) = delete;
    IWriter& operator =(IWriter&&) = delete;
public:
    virtual int WriteRectangle(int left, int top, int width, int height) const = 0;
};

// ----
// #2. Xml 또는 Json으로 저장합니다.
// ----
class XmlWriter : public IWriter {
public:
    XmlWriter() = default;

    virtual int WriteRectangle(int left, int top, int width, int height) const override {
        return 0;
    }
};
class JsonWriter : public IWriter {
public:
    JsonWriter() = default;

    virtual int WriteRectangle(int left, int top, int width, int height) const override {
        return 1;
    }
};    

// ---
// #3. Save()시 설정된 IWriter에 따라 다른 전략으로 저장합니다.
// ---
class Rectangle {
    int m_Left;
    int m_Top;
    int m_Width;
    int m_Height;

    const IWriter* m_Writer;
public:
    Rectangle(int l, int t, int w, int h) : m_Left{l}, m_Top{t}, m_Width{w}, m_Height{h} {}

    int GetLeft() const {return m_Left;}
    int GetTop() const {return m_Top;}
    int GetWidth() const {return m_Width;}
    int GetHeight() const {return m_Height;}

    void SetWriter(const IWriter* writer) {m_Writer = writer;} // #3
    int Save() {
        if (m_Writer) {
            return m_Writer->WriteRectangle(m_Left, m_Top, m_Width, m_Height); // #3
        }
        return -1;
    }
};

// ----
// 테스트 코드
// ----
Rectangle rectangle{0, 0, 10, 20};

XmlWriter xmlWriter;
rectangle.SetWriter(&xmlWriter);
EXPECT_TRUE(rectangle.Save() == 0); // XmlWriter::WriteRectangle()이 실행됩니다.

JsonWriter jsonWriter;
rectangle.SetWriter(&jsonWriter); // #4. 런타임에 전략을 변경합니다.
EXPECT_TRUE(rectangle.Save() == 1); // JsonWriter::WriteRectangle()이 실행됩니다.

댓글남기기