2 minute read

명령(Command) 패턴이란?


명령 패턴은 요청을 객체로 캡슐화하여, 요청을 보낸 객체(Invoker)와 요청을 처리하는 객체(Receiver) 사이의 결합도를 낮추는 행위 디자인 패턴입니다. 이를 통해 요청을 큐에 저장하거나, 로깅하거나, 취소(Undo)/재실행(Redo)하는 등의 기능을 쉽게 구현할 수 있습니다.
마치 식당에서 웨이터가 손님의 주문을 받아 주방에 전달하는 것과 같습니다. 손님(Client)은 웨이터(Invoker)에게 주문(Command)을 하고, 웨이터는 주문 내용을 주방(Receiver)에 전달합니다. 손님은 주방이 어떻게 요리하는지 알 필요가 없고, 웨이터는 각 요리 방법을 알 필요 없이 주문만 전달하면 됩니다.

명령(Command) 패턴의 구성 요소


  • Command (명령 인터페이스): 모든 구체적인 명령 클래스가 구현해야 하는 인터페이스 또는 추상 클래스입니다. Execute() 메서드를 선언하여 명령 실행을 정의합니다.
  • Concrete Command (구체적인 명령): 실제 명령을 구현하는 클래스입니다. Receiver 객체에 대한 참조를 가지고 있으며, Execute() 메서드에서 Receiver의 특정 작업을 호출합니다.
  • Receiver (수신자): 실제 작업을 수행하는 객체입니다. Concrete Command 객체에 의해 호출됩니다.
  • Invoker (요청자): Command 객체를 가지고 있으며, Command의 Execute() 메서드를 호출하여 요청을 실행합니다. Invoker는 어떤 Concrete Command가 실행되는지 알 필요가 없습니다.
  • Client (클라이언트): Concrete Command 객체를 생성하고, Receiver 객체를 연결하여 Invoker에 전달합니다.

명령(Command) 패턴의 작동 방식


  1. Client는 Concrete Command 객체를 생성하고, Receiver 객체를 연결합니다.
  2. Client는 생성된 Command 객체를 Invoker에 전달합니다.
  3. Invoker는 Command 객체의 Execute() 메서드를 호출합니다.
  4. Command 객체의 Execute() 메서드는 연결된 Receiver 객체의 특정 작업을 호출합니다.

명령(Command) 패턴의 장단점


장점

  • 낮은 결합도: Invoker와 Receiver 사이의 결합도를 낮추어 코드의 유연성과 재사용성을 향상시킵니다.
  • 요청 큐잉: 요청을 큐에 저장하여 순차적으로 처리하거나, 특정 조건에 따라 처리를 지연시킬 수 있습니다.
  • 로깅 및 취소/재실행: 요청 내역을 로깅하거나, 실행 취소(Undo) 및 재실행(Redo) 기능을 쉽게 구현할 수 있습니다.
  • 복합 명령: 여러 개의 명령을 하나의 복합 명령으로 묶어서 실행할 수 있습니다.

단점

  • 클래스 증가: 명령의 종류가 많아질수록 클래스의 수가 증가할 수 있습니다.

활용 예시


  • 메뉴 시스템: 각 메뉴 항목을 명령 객체로 캡슐화하여 메뉴 동작을 처리합니다.
  • GUI 버튼: 버튼 클릭 이벤트를 명령 객체로 캡슐화합니다.
  • 게임의 입력 처리: 각 키 입력 또는 마우스 클릭을 명령 객체로 캡슐화하여 게임 로직과 분리합니다.
  • 텍스트 편집기의 Undo/Redo 기능: 각 편집 작업을 명령 객체로 캡슐화하여 Undo/Redo 스택에 저장합니다.

예시코드


#include <iostream>
#include <vector>

// 명령 인터페이스
class Command {
public:
    virtual void Execute() = 0;
    virtual ~Command() {}
};

// 수신자
class Light {
public:
    void TurnOn() {
        std::cout << "Light is ON" << std::endl;
    }

    void TurnOff() {
        std::cout << "Light is OFF" << std::endl;
    }
};

// 구체적인 명령: 전등 켜기
class TurnOnCommand : public Command {
private:
    Light* light_;
public:
    TurnOnCommand(Light* light) : light_(light) {}
    void Execute() override {
        light_->TurnOn();
    }
};

// 구체적인 명령: 전등 끄기
class TurnOffCommand : public Command {
private:
    Light* light_;
public:
    TurnOffCommand(Light* light) : light_(light) {}
    void Execute() override {
        light_->TurnOff();
    }
};

// 요청자
class RemoteControl {
private:
    std::vector<Command*> commands_;
public:
    void SetCommand(Command* command) {
        commands_.push_back(command);
    }

    void PressButton() {
        for(Command* command : commands_){
            command->Execute();
        }
    }
    ~RemoteControl(){
        for(Command* command : commands_){
            delete command;
        }
    }
};

int main() {
    Light light;
    RemoteControl remote;

    remote.SetCommand(new TurnOnCommand(&light));
    remote.SetCommand(new TurnOffCommand(&light));

    remote.PressButton();

    return 0;
}

Top