KimMK

명령(Command) 패턴 본문

C++/디자인 패턴

명령(Command) 패턴

KimMK 2023. 3. 11. 15:16

디자인 패턴 중 행위 패턴인 명령 패턴은 요구사항을 객체로 캡슐화하는 것이다. 실행될 기능을 캡슐화함으로써 주어진 여러 기능을 실행할 수 있는 재사용성이 높은 클래스를 설계하는 패턴으로 하나의 추상 클래스에 메서드를 만들어 각 명령이 들어오면 그에 맞는 서브 클래스가 선택되어 실행되는 특징을 갖는다. 

즉, 함수 호출 자체를 객체로 감싸 캡슐화 한 것이다.

함수 호출을 객체로 만들었기 때문에 디커플링으로 코드가 유연하며, 

redo(재실행), undo(실행 취소), 로깅,  입력키 변경 등과 같은 기능을 구현하는데 유용하다.

 

간단한 예시

리모컨을 제어하는 프로그램을 만든다고 가정할 때, 이 프로그램은 다양한 장치(에어컨, TV...)를 제어하는데 사용한다.

class Device {
public:
    virtual void turnOn() = 0;
    virtual void turnOff() = 0;
};

장치를 제어하는 추상 클래스를 만들고 모든 장치에 필요한 전원을 키고 끌 수 있는 가상 메서드(On/Off)를 포함시켜준다.

 

 

class Command {
public:
    virtual ~Command() {}
    virtual void execute() = 0;
protected:
    Command(Device* device) : device(device) {}
    Device* device;
};

class TurnOnCommand : public Command {
public:
    TurnOnCommand(Device* device) : Command(device) {}
    void execute() override {
        device->turnOn();
    }
};

class TurnOffCommand : public Command {
public:
    TurnOffCommand(Device* device) : Command(device) {}
    void execute() override {
        device->turnOff();
    }
};

그 후, 명령을 캡슐화하는 클래스를 만들고 이 클래스에서 실행할 장치와 메서드를 저장한다.

 

class RemoteControl {
public:
    void pressOnButton(Command* command) {
        command->execute();
    }
    void pressOffButton(Command* command) {
        command->execute();
    }
};

int main() {
    Device* light = new Light();
    Command* turnOnCommand = new TurnOnCommand(light);
    Command* turnOffCommand = new TurnOffCommand(light);

    RemoteControl remoteControl;

    remoteControl.pressOnButton(turnOnCommand);
    remoteControl.pressOffButton(turnOffCommand);

    return 0;
}

리모컨 클래스에서 명령 객체를 생성해 execute 메서드를 통해 명령을 실행하도록 구현한다.

 

Command 패턴 장점 단점
- 유연성
명령을 캡슐화하므로 명령을 실행하는데 유연성을 제공한다. 명령의 인터페이스를 수정하거나 명령 자체를 교체해 새로운 명령을 쉽게 추가할 수 있다.

- 확장성
새로운 명령을 추가할 때, 다른 명령들과 함께 동작하는 것을 변경할 필요없다.

- 명령 기록
명령 패턴을 이용하면 실행된 명령의 기록을 저장할 수 있어 redo, undo, logging과 같은 기능을 제공할 수 있다.

- 이식성
다양한 플랫폼과 언어에서 사용될 수 있으며, 적용 가능한 범위가 넓다. 
클로저를 제대로 지원해주는 언어에서 적합하다.
클로저를 지원하지 않는 언어라면 클로저를 흉내내는 방법 중 하나라고 생각

** C++에서 클래스형으로만 구현하는 것은 함수형에는 적합하지 않기 때문이다. 함수 포인터는 상태를 저장할 수 없고 람다는 메모리를 직접 관리해야 하므로 쓰기가 까다롭다. 또한 람다로 구현할 경우 누가 누굴 호출했는지 알기가 쉽지 않다. **
- 복잡성
명령 패턴은 구현이 비교적 복잡할 수 있다.
명령 객체와 수신 객체 간의 상호 작용을 구성해야 하기 때문이다.

- 실행 속도
명령을 실행하는 데 추가적인 오버헤드가 발생한다. 객체를 생성하고 저장하는 것은 일반적으로 함수 호출보다 오버헤드가 크며, 명령 객체가 쌓이는 메모리 공간이 필요하기 때문에 메모리 사용량이 증가하고 불필요한 객체가 쌓이면 메모리 Leak이 발생할 수 있다.
따라서, 실행 속도가 느려질 수 있다.

- 메모리 사용
실행 속도에서 언급했듯이, 명령 객체를 생성하고 유지해야 하기 때문에 메모리 공간을 할당할 수 밖에 없다. 명령의 수가 쌓이면 메모리 사용량이 증가한다.

 

'C++ > 디자인 패턴' 카테고리의 다른 글

관찰자(Observer) 패턴  (0) 2023.03.14
싱글턴(Singleton) 패턴  (0) 2023.03.12
경량(Flyweight) 패턴  (1) 2023.03.11
디자인 패턴  (0) 2023.03.11
팩토리 패턴  (0) 2023.03.10