본문 바로가기
-----ETC-----/C++ 고급 프로그래밍과 응용 프로젝트 시리즈

[C++ 고급 프로그래밍과 응용 프로젝트 시리즈] Day 13: 디자인 패턴 심화 - 커맨드 패턴 (Command Pattern)

by cogito21_cpp 2024. 8. 1.
반응형

커맨드 패턴 (Command Pattern)

커맨드 패턴은 요청을 객체의 형태로 캡슐화하여 요청자와 수행자 간의 의존성을 줄이는 패턴입니다. 이는 요청을 큐에 저장하거나, 로그로 기록하거나, 되돌릴 수 있는 작업을 구현할 수 있도록 합니다.

 

커맨드 패턴의 특징

  1. 요청 캡슐화: 요청을 객체 형태로 캡슐화하여 요청자와 수행자를 분리합니다.
  2. 커맨드 객체: 요청을 실행하는 메서드를 포함하는 객체입니다.
  3. 되돌리기 가능: 실행된 요청을 되돌리거나 재실행할 수 있습니다.

커맨드 패턴의 구조

  1. Command (커맨드): 실행 메서드를 정의하는 인터페이스
  2. ConcreteCommand (구체적 커맨드): 실제 실행될 작업을 구현하는 클래스
  3. Invoker (호출자): 커맨드를 실행하는 역할
  4. Receiver (수신자): 실제 작업을 수행하는 객체

커맨드 패턴 UML 다이어그램

+-----------+         +-----------------+      +---------+
|  Invoker  |<------->|   Command       |<---->| Receiver|
+-----------+         +-----------------+      +---------+
| -command  |         | +execute()      |
| +execute()|         +-----------------+
+-----------+         | +undo()         |
                      +-----------------+
                           /\
                           ||
                   +-----------------+
                   | ConcreteCommand |
                   +-----------------+
                   | +execute()      |
                   | +undo()         |
                   +-----------------+

커맨드 패턴 구현 예제

다음 예제는 커맨드 패턴을 사용하여 간단한 텍스트 편집기를 구현합니다.

 

Command 인터페이스 정의

#include <iostream>
#include <string>
#include <stack>
#include <memory>

// 커맨드 인터페이스
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

 

Receiver 클래스 정의

// 수신자 클래스
class TextEditor {
private:
    std::string text;

public:
    void appendText(const std::string& newText) {
        text += newText;
    }

    void deleteText(size_t length) {
        if (length > text.size()) {
            text.clear();
        } else {
            text.erase(text.size() - length);
        }
    }

    void showText() const {
        std::cout << "Current text: " << text << std::endl;
    }
};

 

ConcreteCommand 클래스 정의

// 구체적 커맨드 클래스: 텍스트 추가
class AppendCommand : public Command {
private:
    TextEditor& editor;
    std::string text;

public:
    AppendCommand(TextEditor& editor, const std::string& text) : editor(editor), text(text) {}

    void execute() override {
        editor.appendText(text);
    }

    void undo() override {
        editor.deleteText(text.size());
    }
};

 

Invoker 클래스 정의

// 호출자 클래스
class CommandManager {
private:
    std::stack<std::unique_ptr<Command>> history;

public:
    void executeCommand(std::unique_ptr<Command> command) {
        command->execute();
        history.push(std::move(command));
    }

    void undoCommand() {
        if (!history.empty()) {
            history.top()->undo();
            history.pop();
        }
    }
};

 

커맨드 패턴 사용 예제

int main() {
    TextEditor editor;
    CommandManager manager;

    manager.executeCommand(std::make_unique<AppendCommand>(editor, "Hello, "));
    editor.showText();

    manager.executeCommand(std::make_unique<AppendCommand>(editor, "World!"));
    editor.showText();

    manager.undoCommand();
    editor.showText();

    manager.undoCommand();
    editor.showText();

    return 0;
}

 

이 예제에서 TextEditor 클래스는 텍스트를 관리하고, AppendCommand 클래스는 텍스트 추가 작업을 정의합니다. CommandManager 클래스는 커맨드를 실행하고 실행된 커맨드를 기록하여 나중에 되돌릴 수 있도록 합니다.


커맨드 패턴의 응용

커맨드 패턴은 다양한 응용 분야에서 사용할 수 있습니다. 몇 가지 예를 들어보겠습니다.

  1. GUI 버튼 클릭: 버튼 클릭 이벤트를 커맨드 객체로 캡슐화하여 처리합니다.
  2. 매크로 기록기: 사용자 작업을 기록하고 재생하는 매크로 기능을 구현합니다.
  3. 트랜잭션 관리: 데이터베이스 트랜잭션을 커맨드 객체로 캡슐화하여 관리합니다.

실습 문제

문제 1: 커맨드 패턴을 사용하여 간단한 계산기 구현

커맨드 패턴을 사용하여 덧셈과 뺄셈을 수행하는 간단한 계산기를 구현하세요. 계산기는 수행된 명령을 되돌릴 수 있어야 합니다.

 

해설:

Command 인터페이스 정의

#include <iostream>
#include <stack>
#include <memory>

// 커맨드 인터페이스
class Command {
public:
    virtual void execute() = 0;
    virtual void undo() = 0;
    virtual ~Command() = default;
};

 

Receiver 클래스 정의

// 수신자 클래스
class Calculator {
private:
    int value = 0;

public:
    void add(int operand) {
        value += operand;
    }

    void subtract(int operand) {
        value -= operand;
    }

    int getValue() const {
        return value;
    }
};

 

ConcreteCommand 클래스 정의

// 구체적 커맨드 클래스: 덧셈
class AddCommand : public Command {
private:
    Calculator& calculator;
    int operand;

public:
    AddCommand(Calculator& calculator, int operand) : calculator(calculator), operand(operand) {}

    void execute() override {
        calculator.add(operand);
    }

    void undo() override {
        calculator.subtract(operand);
    }
};

// 구체적 커맨드 클래스: 뺄셈
class SubtractCommand : public Command {
private:
    Calculator& calculator;
    int operand;

public:
    SubtractCommand(Calculator& calculator, int operand) : calculator(calculator), operand(operand) {}

    void execute() override {
        calculator.subtract(operand);
    }

    void undo() override {
        calculator.add(operand);
    }
};

 

Invoker 클래스 정의

// 호출자 클래스
class CommandManager {
private:
    std::stack<std::unique_ptr<Command>> history;

public:
    void executeCommand(std::unique_ptr<Command> command) {
        command->execute();
        history.push(std::move(command));
    }

    void undoCommand() {
        if (!history.empty()) {
            history.top()->undo();
            history.pop();
        }
    }
};

 

커맨드 패턴 사용 예제

int main() {
    Calculator calculator;
    CommandManager manager;

    manager.executeCommand(std::make_unique<AddCommand>(calculator, 10));
    std::cout << "Value after adding 10: " << calculator.getValue() << std::endl;

    manager.executeCommand(std::make_unique<SubtractCommand>(calculator, 5));
    std::cout << "Value after subtracting 5: " << calculator.getValue() << std::endl;

    manager.undoCommand();
    std::cout << "Value after undoing subtraction: " << calculator.getValue() << std::endl;

    manager.undoCommand();
    std::cout << "Value after undoing addition: " << calculator.getValue() << std::endl;

    return 0;
}

 

이 예제에서 Calculator 클래스는 계산을 수행하고, AddCommandSubtractCommand 클래스는 각각 덧셈과 뺄셈 작업을 정의합니다. CommandManager 클래스는 커맨드를 실행하고 기록하여 나중에 되돌릴 수 있도록 합니다.

 

이제 13일차의 학습을 마쳤습니다. 커맨드 패턴의 다양한 구현 방법과 활용 사례에 대해 상세히 학습하고 실습 문제를 풀어보았습니다.

 

질문이나 피드백이 있으면 언제든지 댓글로 남겨 주세요. 내일은 "템플릿 메소드 패턴"에 대해 학습하겠습니다.

반응형