3 분 소요

Composite은 단일 개체와 복합 개체를 추상화하여 모두 동일한 방식으로 다루게 해줍니다. 둘을 서로 구분하여 처리할 필요가 없어지기 때문에 고민할 것들이 줄어듭니다.

설명

Tree와 같은 개체 구조를 다룰 때 개별 개체에 접근하려면 하위 개체가 있는지 검사한 뒤, 있다면 하위 개체를 탐색해 주어야 합니다. 약간 번거롭고 빈번히 if()검사를 해야 해서 코드 냄새가 날 수 있는데요,

Composite

Composite는 단일 개체와 복합 개체를 모두 동일한 방식으로 다루게 해줍니다. 코드를 단순하게 유지시켜 주는 장점이 있지만, 단일 개체에서도 복합 개체용 인터페이스들을 구현해야 하는 단점이 있습니다. 아주 대놓고 인터페이스 분리 원칙을 위반합니다.

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

CompositeOperation() 호출시 자신의 하위 m_Childeren들의 Operation()을 모두 호출해 주어, Client가 하위 개체를 일일이 탐색하는 번거로움을 대신해 주고 있습니다. 다만, 단일 개체인 LeafAdd(), Remove(), GetChild()와 같은 복합 개체용 인터페이스를 구현해 주어야 합니다.

Composite

항목 내용
Component 단일 개체와 복합 개체를 동일하게 다룰 수 있는 추상 클래스입니다.
Leaf 단일 개체입니다. 복합 개체용 인터페이스도 구현해야 합니다.
Composite 복합 개체입니다. 하위 개체 추가/삭제/탐색 인터페이스를 제공하며, Operation()실행시 모든 하위 개체에 적용합니다.
Client Composite를 이용하여 각 개체의 Operation()을 실행합니다.

특징

하위 개체를 탐색해야 하는 번거로움은 줄어들지만, Leaf입장에선 억지로 복합 개체용 인터페이스를 구현해야 하므로 인터페이스 분리 원칙제로 오버헤드 원칙을 위반합니다. 이는 하위 개체 접근시에만 다운 캐스팅하여 어느 정도 보완 할 수도 있습니다.

하위 개체 탐색 편의성이냐, 개체지향 원칙 준수냐 사이에서 어느 것이 유지보수에 유리한지 판단하여 결정하시기 바랍니다.(저는 하위 개체 탐색 편의성이 코드가 간결해져 유지보수에 유리하다고 생각합니다.)

예제

UI 컨트롤들은 여러개의 컨트롤을 하위 개체로 사용하는 Panel을 사용하여 관리하면 좋습니다. 이 Panel을 대화상자에 붙이거나, 메인 메뉴에 붙이거나, 작업창에 붙여서 동일한 UI를 손쉽게 재활용 할 수 있습니다.

다음은 각 컨트롤들을 Composite 패턴을 이용하여 Panel로 관리하는 예입니다. Panel은 하위에 일반 컨트롤들이나 또다른 Panel을 추가할 수 있으며, PanelSetEnable()을 호출하면, 모든 하위 컨트롤들의 SetEnable()이 호출됩니다.

  1. #1 : ControlComponent입니다. 단일 개체와 복합 개체용 인터페이스를 함께 정의합니다.
  2. #2 : LabelEdit는 단일 개체의 구현입니다. 이때 복합 개체용 인터페이스 함수들은 사용하지 못하도록 예외를 발생시킵니다.
  3. #3 : PanelComposite 입니다. 이때 SetEnable() 실행시 모든 하위 개체에도 SetEnable()을 호출합니다.
  4. #4 : rootPanel에서 SetEnable()을 호출하면 모든 하위 개체들의 SetEnable()을 실행합니다.
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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
// ----
// #1. Component 입니다. 단일 개체와 복합 개체용 인터페이스를 함께 정의합니다.
// ----
class Control {
protected:
    Control() = default; // 다형 소멸을 제공하는 추상 클래스. 상속해서만 사용하도록 protected
public:
    virtual ~Control() = default; // 다형 소멸 하도록 public virtual
private:
    Control(const Control&) = delete;
    Control(Control&&) = delete;
    Control& operator =(const Control&) = delete;
    Control& operator =(Control&&) = delete;          
public:
    // #1. 단일 개체용 인터페이스입니다.
    virtual void SetEnable(bool val) = 0;

    // #2. 복합 개체용 인터페이스입니다.
    virtual void Add(std::unique_ptr<Control> child) = 0;
    virtual void Remove(int index) = 0;
    virtual Control& GetChild(int index) = 0;
};

// ----
// #2. 단일 개체의 구현입니다. 복합 개체용 함수들은 예외를 발생시킵니다.
// ----
class Label : public Control {
public:
    Label() = default;

        virtual void SetEnable(bool val) override {
        std::cout << "Label::SetEnable()" << std::endl;
    }      
    virtual void Add(std::unique_ptr<Control> child) override {
        throw "Can not support"; // #2
    }
    virtual void Remove(int index) override {
        throw "Can not support"; // #2
    }
    virtual Control& GetChild(int index) override {
        throw "Can not support"; // #2
    }
};
class Edit : public Control {
public:
    Edit() = default;

    virtual void SetEnable(bool val) override {
        std::cout << "Edit::SetEnable()" << std::endl;
    }  
    virtual void Add(std::unique_ptr<Control> child) override {
        throw "Can not support"; // #2
    }
    virtual void Remove(int index) override {
        throw "Can not support"; // #2
    }
    virtual Control& GetChild(int index) override {
        throw "Can not support"; // #2
    }
};

// ----
// #3. 복합 개체인 Composite 입니다. SetEnable() 실행시 모든 하위 개체에 적용합니다.
// ----
class Panel : public Control {
    std::vector<std::shared_ptr<Control>> m_Children;
public:
    Panel() = default;

    // Child들의 SetEnable들을 설정합니다.
    virtual void SetEnable(bool val) override {
        for (auto& child : m_Children) { // #3
            child->SetEnable(val);
        }
    } 
    virtual void Add(std::unique_ptr<Control> child) override {
        assert(child);
        m_Children.emplace_back(child.release());
    }
    virtual void Remove(int index) override {
        assert(index < m_Children.size());

        m_Children.erase(m_Children.begin() + index);
    }
    virtual Control& GetChild(int index) override {
        assert(index < m_Children.size());

        return *m_Children[index];
    }
};

// ----
// 테스트 코드
// ----   
std::unique_ptr<Control> subPanel{new Panel};
subPanel->Add(std::unique_ptr<Control>{new Label});
subPanel->Add(std::unique_ptr<Control>{new Edit});   

Panel rootPanel;
rootPanel.Add(std::unique_ptr<Control>{new Label});
rootPanel.Add(std::unique_ptr<Control>{new Edit});
rootPanel.Add(std::move(subPanel)); // 하위 Panel을 추가합니다.

rootPanel.SetEnable(true); // #4. 모든 하위 개체들의 SetEnable()을 실행합니다.

댓글남기기