명령 패턴(Command Pattern)

  • 일반적인 객체는 프로퍼티(변수)와 메서드(함수)로 구성됩니다.
  • 하지만 명령 패턴을 사용하면 객체의 행동(메서드 호출) 또한 객체로 캡슐화가 가능합니다.
  • 명령 패턴은 동작을 하나의 객체로 캡슐화하여 행위를 실행합니다.

명령 패턴 구조

  • 명령 패턴은 실제 작업을 수행하는 객체와 이를 실행하는 객체로 분리합니다.
  • 명령 패턴은 복수의 명령을 처리하기 위해 객체 간 관계를 정의합니다.
  • 또한 명령 패턴은 다음과 같은 4가지 구성 요소를 갖고 있습니다.
    • 인터페이스(Interface)
      • 동일한 명령 구조와 호출을 위해 인터페이스를 정의합니다.
    • 명령(Command)
      • 명령으로 실행되는 실제 객체들을 구현합니다.
    • 리시버(Receiver)
      • 명령의 실행 동작을 외부로부터 객체로 위임 받아 대신 호출합니다.
    • 인보커(Invoker)
      • 다수의 명령 객체를 저장하는 객체입니다.

명령 패턴: 인터페이스(Interface)

  • 모든 객체에 동일한 실행 메서드를 강제화하기 위해 인터페이스 클래스를 작성합니다.
1
2
3
4
5
6
class Command
{
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
};

명령 패턴: 명령(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
// 일반적인 형태
class Exec1 :
    public Command
{
public:
    Exec1()
    {
        std::cout << "[Exec1] object has concreted" << std::endl;
    }

    void execute() override
    {
        std::cout << "[Exec1] execute run" << std::endl;
    }

    void undo() override
    {
        std::cout << "[Exec1] undo run" << std::endl;
    }
};

// 리시버 활용 형태
class Exec2 :
    public Command
{
public:
    Exec2(std::shared_ptr<Receiver> receiver)
    {
        std::cout << "[Exec2] object has concreted" << std::endl;
        m_receiver = receiver;
    }

    void execute() override
    {
        std::cout << "[Exec2] execute run" << std::endl;
        m_receiver->action1();
        m_receiver->action2();
    }

    void undo() override
    {
        std::cout << "[Exec2] undo run" << std::endl;
    }

private:
    std::shared_ptr<Receiver> m_receiver;
};

명령 패턴: 리시버(Receiver)

  • 명령 패턴은 처리해야 하는 명령을 하나의 객체로 캡슐화합니다.
  • 실제 동작을 처리하는 객체를 작성합니다.
  • 리시버는 클라이언트에서 미리 생성되고, 명령 객체의 인자로 전달됩니다.
  • 리시버를 활용하면 객체의 명령을 미리 만들고, 실행되는 순간을 제어할 수 있습니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Receiver
{
public:
    void action1()
    {
        std::cout << "[Receiver] action1 run" << std::endl;
    }

    void action2()
    {
        std::cout << "[Receiver] action2 run" << std::endl;
    }
};

명령 패턴: 인보커(Invoker)

  • 인보커는 다수의 명령 객체를 관리하는 객체입니다.
  • 인보커는 내부적으로 명령 객체를 저장하고 있는 컨테이너를 사용할 수 있습니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Invoker
{
public:
    void setCommand(std::string key, std::shared_ptr<Command> command)
    {
        m_command[key] = command;
    }

    void remove(std::string key)
    {
        m_command.erase(key);
    }

    void execute(std::string key)
    {
        if (m_command.count(key))
        {
            m_command[key]->execute();
        }
    }

protected:
    std::map<std::string, std::shared_ptr<Command>> m_command;
};

명령 패턴: 실행 코드

  • 클라이언트에서 리시버, 명령을 받을 객체, 인보커를 생성하려 관리할 수 있습니다.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
int main(const int argc, const char* argv[])
{
    std::shared_ptr<Exec1> exec1 = std::make_shared<Exec1>();

    std::shared_ptr<Receiver> receiver = std::make_shared<Receiver>();
    std::shared_ptr<Exec2> exec2 = std::make_shared<Exec2>(receiver);

    std::shared_ptr<Invoker> invoker = std::make_shared<Invoker>();
    invoker->setCommand("cmd1", exec1);
    invoker->setCommand("cmd2", exec2);

    invoker->execute("cmd1");
    invoker->execute("cmd2");

    return 0;
}
  • 실행 결과
1
2
3
4
5
6
[Exec1] object has concreted
[Exec2] object has concreted
[Exec1] execute run
[Exec2] execute run
[Receiver] action1 run
[Receiver] action2 run
  • 명령 패턴은 객체의 실행을 취소하는 동작인 undo()로 확장 가능합니다.
  • 명령을 수행하기 전 상태를 저장하는 프로퍼티를 생성하고, undo() 명령에서 복구하는 방법으로 사용합니다.
  • 이러한 명령 패턴은 어플리케이션의 메뉴 처리, CLI(Command Line Interface)와 같은 환경에서 사용합니다.