Menu
CoddyTech

커맨드 패턴

Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 96번째.

명령 패턴은 요청을 객체로 캡슐화하여, 클라이언트에 서로 다른 요청을 매개변수로 전달하거나, 작업을 대기열에 넣거나, 실행 취소 기능을 지원할 수 있게 합니다. 또한 작업을 호출하는 객체와 작업을 수행하는 방법을 아는 객체를 분리합니다.

이 패턴에는 네 가지 핵심 구성 요소가 있습니다. execute() 메서드가 있는 Command 인터페이스, 특정 동작을 구현하는 Concrete Commands, 실제 작업을 수행하는 Receiver, 그리고 명령을 실행하는 Invoker입니다.

#include <iostream>
#include <memory>
#include <vector>

// 수신자 - 작업을 수행하는 방법을 알고 있음
class Light {
public:
    void turnOn() { std::cout << "Light is ON\n"; }
    void turnOff() { std::cout << "Light is OFF\n"; }
};

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

// 구체적인 Command 클래스들
class LightOnCommand : public Command {
    Light& light;
public:
    LightOnCommand(Light& l) : light(l) {}
    void execute() override { light.turnOn(); }
    void undo() override { light.turnOff(); }
};

class LightOffCommand : public Command {
    Light& light;
public:
    LightOffCommand(Light& l) : light(l) {}
    void execute() override { light.turnOff(); }
    void undo() override { light.turnOn(); }
};

// 호출자
class RemoteControl {
    std::vector<std::unique_ptr<Command>> history;
public:
    void pressButton(std::unique_ptr<Command> cmd) {
        cmd->execute();
        history.push_back(std::move(cmd));
    }
    
    void pressUndo() {
        if (!history.empty()) {
            history.back()->undo();
            history.pop_back();
        }
    }
};

각 명령은 수신자에 대한 참조를 저장하고 실행될 때 적절한 메서드를 호출합니다. 호출자는 어떤 동작이 일어날지 알지 못합니다. 그저 execute()를 호출할 뿐입니다. 명령을 기록에 저장하면 각 명령의 undo() 메서드를 호출하여 쉽게 실행 취소를 구현할 수 있습니다.

작업을 대기열에 넣거나 실행 취소/다시 실행을 구현하거나 요청을 보내는 쪽과 처리기를 분리해야 할 때 Command를 사용하세요.

challenge icon

챌린지

쉬움

명령 패턴을 사용해 실행 취소 기능이 있는 텍스트 편집기를 만들어 보겠습니다. 텍스트 삽입 및 삭제와 같은 편집 작업을 명령 객체로 캡슐화하여, 사용자가 작업을 실행한 다음 실제 텍스트 편집기처럼 역순으로 실행 취소할 수 있는 시스템을 만들 것입니다.

코드를 네 개의 파일로 구성합니다.

  • TextDocument.h: 실제 텍스트 내용을 저장하고 조작하는 리시버 클래스를 만듭니다.

    TextDocument 클래스는 문서 내용을 문자열로 저장해야 합니다. 다음 메서드를 구현하세요.

    • insertText(const std::string& text): 내용을 끝에 텍스트를 추가합니다.
    • deleteText(int count): 내용의 마지막 count개 문자를 제거합니다(count가 내용의 길이를 초과하면 아무 작업도 하지 않습니다).
    • getContent(): 현재 내용을 const 참조로 반환합니다.
  • Command.h: 명령 인터페이스와 구체적인 명령 클래스를 정의합니다.

    순수 가상 메서드 execute()와 undo(), 그리고 가상 소멸자를 갖는 추상 Command 클래스를 만듭니다.

    그런 다음 구체적인 명령 두 가지를 구현합니다.

    • InsertCommand: TextDocument 참조와 삽입할 문자열을 받습니다. 실행하면 텍스트를 삽입합니다. 실행 취소하면 삽입한 문자와 같은 개수의 문자를 삭제합니다.
    • DeleteCommand: TextDocument 참조와 삭제할 문자 개수를 받습니다. 실행 취소할 때 복원할 수 있도록 삭제한 텍스트를 저장해야 합니다. 실행하면 끝에서부터 문자를 제거합니다. 실행 취소하면 저장된 텍스트를 다시 삽입합니다.
  • TextEditor.h: 명령 실행과 기록을 관리하는 인보커를 만듭니다.

    TextEditor 클래스는 TextDocument에 대한 참조를 보유하고, unique_ptr<Command>의 벡터를 사용해 실행한 명령의 기록을 유지해야 합니다. 다음을 구현하세요.

    • executeCommand(std::unique_ptr<Command> cmd): 명령을 실행하고 기록에 추가합니다.
    • undo(): 가장 최근 명령을 실행 취소하고 기록에서 제거합니다(기록이 비어 있으면 아무 작업도 하지 않습니다).
    • showContent(): 현재 문서 내용을 출력합니다. 내용이 비어 있으면 [empty]를 출력합니다.
  • main.cpp: 실행 취소 기능을 갖춘 명령 패턴을 보여 줍니다.

    입력 세 개를 읽습니다.

    1. 삽입할 첫 번째 텍스트(문자열)
    2. 삽입할 두 번째 텍스트(문자열)
    3. 삭제할 문자 수(정수)

    TextDocument와 TextEditor를 만듭니다. 그런 다음 다음 작업을 순서대로 수행하고, 각 단계가 끝날 때마다 내용을 표시합니다.

    1. 첫 번째 텍스트를 삽입한 다음 내용을 표시합니다.
    2. 두 번째 텍스트를 삽입한 다음 내용을 표시합니다.
    3. 지정된 문자 수를 삭제한 다음 내용을 표시합니다.
    4. 한 번 실행 취소한 다음 내용을 표시합니다.
    5. 한 번 더 실행 취소한 다음 내용을 표시합니다.

예를 들어 입력이 Hello, World, 3인 경우:

Hello
Hello World
Hello Wo
Hello World
Hello

입력이 Code, Editor, 6인 경우:

Code
CodeEditor
Code
CodeEditor
Code

각 명령은 자신을 어떻게 되돌리는지 알고 있습니다. DeleteCommand는 삭제한 내용을 기억해 복원할 수 있고, InsertCommand는 실행 취소 시 정확히 몇 개의 문자를 제거해야 하는지 알고 있습니다. 이것이 명령 패턴의 장점입니다. 작업을 저장하고 실행하고 되돌릴 수 있는 일급 객체로 만들 수 있습니다.

직접 해보기

#include <iostream>
#include <string>
#include <memory>
#include "TextDocument.h"
#include "Command.h"
#include "TextEditor.h"

int main() {
    // 입력값 읽기
    std::string firstText;
    std::string secondText;
    int deleteCount;
    
    std::getline(std::cin, firstText);
    std::getline(std::cin, secondText);
    std::cin >> deleteCount;

    // TODO: TextDocument와 TextEditor 생성하기
    
    // TODO: 다음 작업을 순서대로 수행하고, 각 작업 후 내용을 표시하기:
    // 1. 첫 번째 텍스트를 삽입한 다음 내용 표시하기
    // 2. 두 번째 텍스트를 삽입한 다음 내용 표시하기
    // 3. 지정된 수의 문자를 삭제한 다음 내용 표시하기
    // 4. 한 번 실행 취소한 다음 내용 표시하기
    // 5. 한 번 더 실행 취소한 다음 내용 표시하기
    
    // 힌트: 명령을 생성하려면 std::make_unique<InsertCommand>(...)와 
    //       std::make_unique<DeleteCommand>(...)를 사용하세요

    return 0;
}
quiz icon실력 점검

이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.

객체 지향 프로그래밍의 모든 레슨

직접 연습해 보세요: 온라인 C++ 컴파일러