커맨드 패턴
Coddy C++ 여정의 객체 지향 프로그래밍 섹션에 포함된 레슨. 104개 중 96번째.
Command 패턴은 요청을 객체로 캡슐화하여 클라이언트에 다양한 요청을 매개변수로 전달하고, 작업을 대기열에 추가하거나 실행 취소 기능을 지원할 수 있도록 합니다. 이 패턴은 작업을 호출하는 객체와 해당 작업을 수행하는 방법을 알고 있는 객체를 분리합니다.
이 패턴에는 네 가지 핵심 구성 요소가 있습니다. execute() 메서드를 사용하는 Command 인터페이스, 특정 작업을 구현하는 Concrete Commands, actual 작업을 수행하는 Receiver, 그리고 commands를 트리거하는 Invoker입니다:
#include <iostream>
#include <memory>
#include <vector>
// Receiver - 작업을 수행하는 방법을 알고 있음
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;
};
// Concrete Commands
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(); }
};
// Invoker
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();
}
}
};각 command는 수신자에 대한 참조를 저장하고 실행될 때 적절한 메서드를 호출합니다. 호출자는 어떤 동작이 발생할지 알지 못합니다. 단지 execute()를 호출할 뿐입니다. command를 기록에 저장하면 각 command의 undo() 메서드를 호출하여 실행 취소를 쉽게 구현할 수 있습니다.
작업을 대기열에 추가하거나, 실행 취소/다시 실행을 구현하거나, 요청을 보낸 주체와 처리 주체를 분리해야 할 때 Command를 사용하세요.
챌린지
쉬움Command 패턴을 사용하여 실행 취소 기능이 있는 Text Editor를 만들어 보겠습니다. 텍스트 삽입 및 삭제와 같은 편집 작업을 명령 객체로 캡슐화하여, 실제 텍스트 편집기처럼 사용자가 작업을 실행한 다음 역순으로 실행 취소할 수 있는 시스템을 만들게 됩니다.
코드를 네 개의 파일로 구성합니다.
TextDocument.h: 실제 텍스트 content를 보유하고 조작하는 receiver class를 생성합니다.TextDocumentclass는 document의 content를 string으로 저장해야 합니다. 다음 메서드를 구현합니다.insertText(const std::string& text): content의 끝에 text를 추가합니다.deleteText(int count): content에서 마지막count개의 characters를 제거합니다(count가 content 길이를 exceeds 경우 아무 작업도 하지 않음).getContent(): 현재 content를 const reference로 반환합니다.
Command.h: command interface와 concrete command classes를 define합니다.pure virtual methods인
execute()와undo(), 그리고 virtual destructor를 포함하는 abstractCommandclass를 생성합니다.그런 다음 두 개의 concrete commands를 implement합니다.
InsertCommand:TextDocument에 대한 reference와 삽입할 string을 받습니다. execute하면 text를 삽입합니다. undo하면 삽입된 것과 동일한 수의 characters를 delete합니다.DeleteCommand:TextDocument에 대한 reference와 삭제할 characters의 count를 받습니다. undo 시 복원할 수 있도록 deleted text를 저장해야 합니다. execute하면 끝에서 characters를 제거합니다. undo하면 저장된 text를 다시 삽입합니다.
TextEditor.h: command execution과 history를 관리하는 invoker를 생성합니다.TextEditorclass는TextDocument에 대한 reference를 보유하고unique_ptr<Command>의 vector를 사용하여 executed commands의 history를 유지해야 합니다. 다음을 implement합니다.executeCommand(std::unique_ptr<Command> cmd): command를 execute하고 history에 추가합니다.undo(): 가장 최근 command를 undo하고 history에서 제거합니다(history가 empty이면 아무 작업도 하지 않음).showContent(): 현재 document content를 출력하거나, content가 empty이면[empty]를 출력합니다.
main.cpp: undo functionality를 사용하여 Command 패턴을 demonstrate합니다.세 가지 입력을 읽습니다.
- 삽입할 first text(string)
- 삽입할 second text(string)
- 삭제할 characters의 수(integer)
TextDocument와TextEditor를 생성합니다. 그런 다음 다음 작업을 순서대로 수행하고 각 단계가 끝날 때 content를 표시합니다.- first text를 삽입한 다음 content를 표시합니다.
- second text를 삽입한 다음 content를 표시합니다.
- 지정된 수의 characters를 delete한 다음 content를 표시합니다.
- 한 번 undo한 다음 content를 표시합니다.
- 한 번 더 undo한 다음 content를 표시합니다.
예를 들어 입력이 Hello, World, 3인 경우:
Hello
Hello World
Hello Wo
Hello World
Hello입력이 Code, Editor, 6인 경우:
Code
CodeEditor
Code
CodeEditor
Code각 command가 자신을 어떻게 reverse하는지 확인해 보세요. DeleteCommand는 무엇을 delete했는지 기억하여 복원할 수 있고, InsertCommand는 undo할 때 제거해야 하는 characters의 정확한 수를 알고 있습니다. 이것이 Command 패턴의 힘입니다. 작업이 저장, 실행 및 reverse될 수 있는 first-class objects가 됩니다.
직접 해보기
#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;
}
이 레슨에는 짧은 퀴즈가 포함되어 있습니다. 레슨을 시작해 문제를 풀고 진행 상황을 기록하세요.
객체 지향 프로그래밍의 모든 레슨
직접 연습해 보세요: 온라인 C++ 컴파일러