본문 바로가기
까망 동네/디자인 패턴

[디자인패턴] 커맨드 패턴 (Command Pattern)

by 까망 하르방 2023. 11. 3.
반응형

🎈 커맨드 패턴 (Command Pattern)

• 명령을 캡슐화하고 실행하기 위해 사용

• GUI 응용 프로그램, 트랜잭션 처리, 큐 관리 등에 활용 가능

• 명령 이력 관리, 역명령 실행(Undo), 동적 선택과 같은 상황에서 유용

• 디자인 패턴 중 행위 패턴에 해당된다

 

💻 디자인 패턴(Design Pattern)이란?

👨‍💻 디자인 패턴(Design Pattern)이란? • SW 개발 방법 중에서도 구조적인 문제 해결에 목적을 둔다. • 알고리즘과 같이 특정 문제를 해결하는 Logic 형태보다는 특정 상황에 적용할 수 있는 방

zoosso.tistory.com

 

 

[예제 코드]  Command Pattern

#include <iostream>
#include <vector>

class Light {
public:
    void turnOn() {
        std::cout << "전원 on..." << std::endl;
    }

    void turnOff() {
        std::cout << "전원 off..." << std::endl;
    }
};

class Command {
public:
    virtual void execute() = 0;
};

class TurnOnLightCommand : public Command {
private:
    Light& light;

public:
    TurnOnLightCommand(Light& l) : light(l) {}

    void execute() {
        light.turnOn();
    }
};

class TurnOffLightCommand : public Command {
private:
    Light& light;

public:
    TurnOffLightCommand(Light& l) : light(l) {}

    void execute() {
        light.turnOff();
    }
};

// Invoker (호출자) 클래스
class RemoteControl {
private:
    std::vector<Command*> commands;

public:
    void addCommand(Command* cmd) {
        commands.push_back(cmd);
    }

    void pressButton(int index) {
        if (index >= 0 && index < commands.size()) {
            commands[index]->execute();
        }
        else {
            std::cout << "유효하지 않은 버튼!" << std::endl;
        }
    }
};

int main() {
    Light light;
    TurnOnLightCommand turnOnCmd(light);
    TurnOffLightCommand turnOffCmd(light);

    RemoteControl remote;
    remote.addCommand(&turnOnCmd);
    remote.addCommand(&turnOffCmd);

    remote.pressButton(0); // 전원 on.
    remote.pressButton(1); // 전원 off.
    remote.pressButton(5); // 유효하지 않은 버튼.


    return 0;
}

 

커맨드 패턴 예시 결과

 

전원을 켜고 끄는 리모컨을 구현하였다.

Command 패턴으로 리모컨 버튼과 명령을 캡슐화 처리

 

 

[예제 코드] → Command Pattern 적용 X

명령어 패턴을 적용하지 않는다면 어떻게 될까?

#include <iostream>

class Light {
public:
    void turnOn() {
        std::cout << "전원을 켭니다." << std::endl;
    }
    void turnOff() {
        std::cout << "전원을 끕니다." << std::endl;
    }
};

int main() {
    Light light;

    // 전원 켜기
    light.turnOn();

    // 전원 끄기
    light.turnOff();

    return 0;
}

 

커맨드 패턴 예시 결과

 

Command 패턴을 적용하지 않는다면

리모컨 버튼을 누를 때마다 직접적으로 Light 객체 메서드 호출이 필요하다.

이는 리모컨 버튼과 전원 제어 코드가 강하게 결합된 셈이다.


 

🎈 커맨드 패턴 장단점

+ 확장성: 새롭게 명령 추가하거나 기존 명령어 변경이 쉽다.

+ Undo 실행: 명령 이력을 기록하고 취소를 쉽게 구현할 수 있다.

+ 클라이언트 코드 간소화: 명령 수행 객체 세부 내용을 알 필요가 없어진다.

 

- 클래스 수 증가: 명령마다 별도의 클래스가 필요하므로 클래스 수가 증가

- 복잡성 증가: 간단한 명령의 경우 패턴 적용이 더 복잡해 보인다.

 

 

[예제 코드] → Undo 명령어

#include <iostream>
#include <string>
#include <vector>

// Receiver (장치) 클래스: 텍스트 에디터
class TextEditor {
public:
    void openDocument(const std::string& document) {
        std::cout << "문서 열기: " << document << std::endl;
    }

    void closeDocument(const std::string& document) {
        std::cout << "문서 닫기: " << document << std::endl;
    }
};

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


// ConcreteCommand (구체적인 명령) 클래스
class OpenDocumentCommand : public Command {
private:
    TextEditor& editor;
    std::string document;

public:
    OpenDocumentCommand(TextEditor& e, const std::string& doc) : editor(e), document(doc) {}


    void execute() override {
        editor.openDocument(document);
    }
};

class CloseDocumentCommand : public Command {
private:
    TextEditor& editor;
    std::string document;

public:
    CloseDocumentCommand(TextEditor& e, const std::string& doc) : editor(e), document(doc) {}

    void execute() override {
        editor.closeDocument(document);
    }
};

// Invoker (호출자) 클래스
class TextEditorInvoker {
private:
    std::vector<Command*> commandHistory;

public:
    void executeCommand(Command* cmd) {
        cmd->execute();
        commandHistory.push_back(cmd);
    }

    void undoLastCommand() {
        if (!commandHistory.empty()) {
            Command* lastCommand = commandHistory.back();
            lastCommand->execute();  // 역명령 실행
            commandHistory.pop_back();
        }
        else {
            std::cout << "명령 이력이 없습니다." << std::endl;
        }
    }
};

int main() {
    TextEditor editor;
    TextEditorInvoker invoker;

    // 명령 생성
    OpenDocumentCommand openCmd(editor, "example.txt");
    CloseDocumentCommand closeCmd(editor, "example.txt");

    // 명령 실행
    invoker.executeCommand(&openCmd);
    invoker.executeCommand(&closeCmd);

    // 명령 실행 취소 (Undo)
    invoker.undoLastCommand();

    return 0;
}

 

커맨드 패턴 예시 결과

 

TextEditorInvoker 클래스는 명령을 관리하고 실행하며

실행 이력을 유지하고 명령 실행 취소를 지원한다.


🎈 다른 디자인 패턴과 비교

• Observer

관찰자 패턴은 주체(Subject)와 관찰자(Observer)를 분리하여

주체의 상태 변화를 관찰자에게 알려주는 패턴으로

주로 상태 변경 알림 및 이벤트 처리에 사용된다.

커맨드 패턴은 실행 요청을 수신하고 처리할 객체에 전달하는 것에서 비슷한 점이 보인다.

 

 

• 전략 (Strategy)

전략 패턴은 알고리즘을 정의하고, 각각을 캡슐화하며 런타임시간에 교체 가능하다.

커맨드 패턴은 명령을 객체로 캡슐화하는 것에서는 비슷한점이 있다.

 

💻 [디자인패턴] 전략 패턴 (Strategy Pattern)

전략 패턴 (Strategy Pattern) 이란? 전략(Strategy)은 코드 내부에서 로직(Logic)을 처리하는 「알고리즘」 어떤 목적 달성을 위한 수행 방식이라고 생각하면 좋다. 영화관에서 이벤트 영화 예매 방식을

zoosso.tistory.com

 

 

• 상태 (State)

상태 패턴은 객체의 내부 상태를 변경하고

해당 상태에 따라 객체의 행동을 다르게 처리한다

 

상태 패턴 (State Pattern)

상태(State) 패턴이란? • 객체 내부 상태에 맞춰 스스로 행동을 변경하는 패턴 • 객체는 마치 자신의 클래스를 바꾸는 것처럼 보인다. • if-else와 같은 분기문으로 상태전이 하는 것을 해소한다.

zoosso.tistory.com

 

 

• 팩토리 (Factory)

Factory 패턴은 객체를 생성하기 위한 인터페이스를 정의하고

서브클래스에서 구체적인 생성 과정 결정

주로 객체 생성 및 초기화에 사용됩니다.

 

팩토리 메서드 패턴 (Factory Method)

🎈 팩토리 메서드 패턴(Factory Method Pattern) • 객체 생성 시 확장을 쉽게 하기 위한 설계 방법 강력한 결합 관계는 코드의 수정•변경을 어렵게 한다. • 객체 생성 동작을 별도 클래스로 분리하

zoosso.tistory.com

반응형

댓글