4 분 소요

Command는 기능의 요청과 실행을 분리하여, 실행 부분이 재활용될 수 있게 합니다. 동일한 기능을 메뉴, 단축키, 매크로등 여러 방법으로 실행할 수 있을때 사용하면 좋습니다.

설명

보통은 메뉴에 있는 버튼이 클릭되었을 때 해당 이벤트 핸들러에서 주어진 기능을 실행합니다.

1
2
3
4
5
6
void OpenButton::Clicked() {
    MyApp::Open();
}
void SaveButton::Clicked() {
    MyApp::Save();
}

하지만 이러한 방식은 UI에 종속적이어서 UI가 변경되면 이에 따른 많은 수정이 필요하며, 만약 Open()을 실행하는 UI가 메뉴, 버튼, 단축키 등 여러가지가 있을 경우 MyApp::Open()은 중복해서 작성해야 합니다. 코드가 중복되니 불쾌한 코드 냄새를 유발하죠.

이러한 경우 Command 패턴을 이용하여 기능 요청부와 실행부를 분리해 두면, 실행부를 재활용할 수 있습니다.

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

Invoker는 버튼등으로서 기능을 요청하고, CommandExecute()에서 실제 기능을 실행합니다. 이때 여러개의 Invoker에서 동일한 Command를 사용할 수도 있습니다.

ConcreteCommand에서는 Receiver를 이용하여 기능을 실행합니다. 이때 m_State에 상태값을 저장하여 Undo를 구현할 수도 있습니다.

Command

특징

Execute()시의 상태값을 저장하여 Undo()를 구현할 수 있으며(Mememto 참고), Composite 패턴을 활용하여 여러 Command들을 매크로처럼 실행할 수 있고, UI와 Command를 직접 매칭하는 사용자 정의 기능을 구현할 수 있습니다.

또한, Command들의 갯수가 많다면, 자주 사용하는 것들만 먼저 생성하고, 자주 사용하지 않는 것들은 나중에 필요할 때 생성하여, 제품의 초기 실행 속도를 향상시킬 수도 있습니다.

예제

다음은 어플리케이션의 기능 실행을 Command 패턴으로 구현한 예입니다. Button에 각 Command들을 연결하였으며, Button 클릭시 MyApp의 각 기능들을 실행합니다.

  1. #1 : Command입니다. Execute()를 제공합니다.
  2. #2 : Invoker입니다. Click()시 연결된 CommandExecute()합니다.
  3. #3 : Receiver입니다. CommandExecute()시 실행할 인터페이스입니다.
  4. #4 : MyAppReceiver를 구체화한 클래스 입니다.
  5. #5 : Command를 구체화한 클래스들입니다.
  6. #6 : CompositeCommand는 여러개의 Command들을 Execute()합니다. 매크로 구현시 활용할 수 있습니다.
  7. #7 : ButtonCommand를 연결해 두면, ButtonClick()시 연결된 Command가 실행됩니다.
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
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// ----
// #1. Command 입니다. Execute()를 제공합니다.
// ----
class Command {
protected:
    Command() = default; // 다형 소멸을 제공하는 추상 클래스. 상속해서만 사용하도록 protected
public:
    virtual ~Command() = default; // 다형 소멸 하도록 public virtual   
private:
    Command(const Command&) = delete; 
    Command(Command&&) = delete; 
    Command& operator =(const Command&) = delete; 
    Command& operator =(Command&&) = delete;   
public:
    virtual void Execute() = 0;     
};
// ----
// #2. Invoker 입니다. Click()시 연결된 Command를 Execute() 합니다.
// ----
class Button {
    std::shared_ptr<Command> m_Command;
public:
    explicit Button(std::shared_ptr<Command> command) : m_Command{command} {}
    ~Button() = default;
private:
    Button(const Button&) = delete; 
    Button(Button&&) = delete; 
    Button& operator =(const Button&) = delete; 
    Button& operator =(Button&&) = delete;   
public:
    void Click() {
        m_Command->Execute(); // #2
    }
};

// ----
// #3. Receiver 입니다. Command의 Execute()시 실행할 인터페이스입니다.
// ----
class ICommandReceiver {
protected:
    ICommandReceiver() = default; // 인터페이스여서 상속해서만 사용하도록 protected
    ~ICommandReceiver() = default; // 인터페이스여서 다형 소멸을 하지 않으므로 protected non-virtual
private:
    ICommandReceiver(const ICommandReceiver&) = delete;
    ICommandReceiver(ICommandReceiver&&) = delete;
    ICommandReceiver& operator =(const ICommandReceiver&) = delete;
    ICommandReceiver& operator =(ICommandReceiver&&) = delete;   
public:
    virtual void Open() = 0;
    virtual void Save() = 0;
    virtual void Copy() = 0;
    virtual void Paste() = 0;
};
// ----
// #4. Receiver 를 구체화한 클래스 입니다.
// ----
class MyApp : public ICommandReceiver {
    public:
    MyApp() = default; 
    ~MyApp() = default;
private:
    MyApp(const MyApp&) = delete; 
    MyApp(MyApp&&) = delete; 
    MyApp& operator =(const MyApp&) = delete; 
    MyApp& operator =(MyApp&&) = delete; 

    virtual void Open() override {std::cout << "MyApp::Open()" << std::endl;}
    virtual void Save() override {std::cout << "MyApp::Save()" << std::endl;}
    virtual void Copy() override {std::cout << "MyApp::Copy()" << std::endl;}
    virtual void Paste() override {std::cout << "MyApp::Paste()" << std::endl;}               
};
// ----
// #5. Command 를 구체화한 클래스 입니다.
// ----
class OpenCommand : public Command {
    ICommandReceiver& m_Receiver;
public:
    explicit OpenCommand(ICommandReceiver& receiver) : m_Receiver{receiver} {}

    virtual void Execute() override {m_Receiver.Open();}
};
class SaveCommand : public Command {
    ICommandReceiver& m_Receiver;
public:
    explicit SaveCommand(ICommandReceiver& receiver) : m_Receiver{receiver} {}

    virtual void Execute() override {m_Receiver.Save();}
};
class CopyCommand : public Command {
    ICommandReceiver& m_Receiver;
public:
    explicit CopyCommand(ICommandReceiver& receiver) : m_Receiver{receiver} {}

    virtual void Execute() override {m_Receiver.Copy();}
};
class PasteCommand : public Command {
    ICommandReceiver& m_Receiver;
public:
    explicit PasteCommand(ICommandReceiver& receiver) : m_Receiver{receiver} {}

    virtual void Execute() override {m_Receiver.Paste();}
}; 

// ----
// #6. 여러개의 Command 들을 Execute() 합니다. 매크로 구현시 활용할 수 있습니다.
// ----   
class CompositeCommand : public Command {
    std::vector<std::shared_ptr<Command>> m_Children;    

public:
    CompositeCommand() = default;

    virtual void Execute() override {
        for (auto& child : m_Children) { // 하위의 모든 Command를 실행합니다.
            child->Execute();
        }
    }
    void Add(std::shared_ptr<Command> child) {
        assert(child);
        m_Children.push_back(child);
    }
};

// ----
// 테스트 코드
// ----
MyApp myApp;

// Command 들을 생성합니다.
std::shared_ptr<Command> openCommand{new OpenCommand{myApp}};
std::shared_ptr<Command> saveCommand{new SaveCommand{myApp}};
std::shared_ptr<Command> copyCommand{new CopyCommand{myApp}};
std::shared_ptr<Command> pasteCommand{new PasteCommand{myApp}};
std::shared_ptr<CompositeCommand> copyPasteCommand{new CompositeCommand{}};
copyPasteCommand->Add(copyCommand); // #6. 여러개의 Command를 추가합니다.
copyPasteCommand->Add(pasteCommand);   

// #7. 버튼과 연결합니다.
Button openButton{openCommand};
Button saveButton{saveCommand};
Button copyButton{copyCommand};
Button pasteButton{pasteCommand};
Button copyPasteButton{copyPasteCommand};

// #7. 버튼 클릭시 Command가 실행됩니다.
openButton.Click();
saveButton.Click();
copyButton.Click();
pasteButton.Click();
copyPasteButton.Click(); // #6. 여러개의 Command가 실행됩니다.

댓글남기기