C++ Command 패턴
요청을 객체로 캡슐화하여 실행 취소, 재실행, 큐잉이 가능한 커맨드 시스템입니다. 텍스트 에디터의 편집 히스토리 관리를 예시로 구현합니다.
#include <iostream>
#include <memory>
#include <stack>
#include <string>
#include <vector>
#include <stdexcept>
// ── 커맨드 인터페이스 ─────────────────────────────────────────────────────────
class Command {
public:
virtual ~Command() = default;
virtual void execute() = 0;
virtual void undo() = 0;
virtual std::string description() const = 0;
};
// ── 수신자(Receiver): 텍스트 문서 ─────────────────────────────────────────────
class Document {
public:
void insertAt(std::size_t pos, const std::string& text) {
if (pos > content_.size())
throw std::out_of_range("삽입 위치가 범위를 벗어났습니다.");
content_.insert(pos, text);
}
void eraseAt(std::size_t pos, std::size_t len) {
if (pos + len > content_.size())
throw std::out_of_range("삭제 범위가 범위를 벗어났습니다.");
content_.erase(pos, len);
}
std::string getContent() const { return content_; }
std::size_t size() const { return content_.size(); }
private:
std::string content_;
};
// ── 구체 커맨드: 텍스트 삽입 ─────────────────────────────────────────────────
class InsertCommand final : public Command {
public:
InsertCommand(Document& doc, std::size_t pos, std::string text)
: doc_(doc), pos_(pos), text_(std::move(text)) {}
void execute() override { doc_.insertAt(pos_, text_); }
void undo() override { doc_.eraseAt(pos_, text_.size()); }
std::string description() const override {
return "삽입(pos=" + std::to_string(pos_) + ", \"" + text_ + "\")";
}
private:
Document& doc_;
std::size_t pos_;
std::string text_;
};
// ── 구체 커맨드: 텍스트 삭제 ─────────────────────────────────────────────────
class EraseCommand final : public Command {
public:
EraseCommand(Document& doc, std::size_t pos, std::size_t len)
: doc_(doc), pos_(pos), len_(len) {}
void execute() override {
// 실행 전 삭제될 텍스트를 저장해 undo 에 사용
saved_ = doc_.getContent().substr(pos_, len_);
doc_.eraseAt(pos_, len_);
}
void undo() override { doc_.insertAt(pos_, saved_); }
std::string description() const override {
return "삭제(pos=" + std::to_string(pos_)
+ ", len=" + std::to_string(len_) + ")";
}
private:
Document& doc_;
std::size_t pos_;
std::size_t len_;
std::string saved_; // undo 를 위해 보관
};
// ── 매크로 커맨드: 여러 커맨드를 하나로 묶음 ─────────────────────────────────
class MacroCommand final : public Command {
public:
void add(std::shared_ptr<Command> cmd) {
commands_.push_back(std::move(cmd));
}
void execute() override {
for (auto& cmd : commands_) cmd->execute();
}
void undo() override {
// 역순으로 취소
for (auto it = commands_.rbegin(); it != commands_.rend(); ++it)
(*it)->undo();
}
std::string description() const override {
return "매크로(" + std::to_string(commands_.size()) + "개 커맨드)";
}
private:
std::vector<std::shared_ptr<Command>> commands_;
};
// ── 인보커: 커맨드 히스토리 & 실행/취소 관리 ─────────────────────────────────
class CommandHistory {
public:
void execute(std::shared_ptr<Command> cmd) {
cmd->execute();
std::cout << "[실행] " << cmd->description() << '\n';
undoStack_.push(std::move(cmd));
// 새 커맨드 실행 시 redo 스택 초기화
while (!redoStack_.empty()) redoStack_.pop();
}
void undo() {
if (undoStack_.empty()) {
std::cout << "[취소] 취소할 작업이 없습니다.\n";
return;
}
auto cmd = undoStack_.top(); undoStack_.pop();
cmd->undo();
std::cout << "[취소] " << cmd->description() << '\n';
redoStack_.push(std::move(cmd));
}
void redo() {
if (redoStack_.empty()) {
std::cout << "[재실행] 재실행할 작업이 없습니다.\n";
return;
}
auto cmd = redoStack_.top(); redoStack_.pop();
cmd->execute();
std::cout << "[재실행] " << cmd->description() << '\n';
undoStack_.push(std::move(cmd));
}
private:
std::stack<std::shared_ptr<Command>> undoStack_;
std::stack<std::shared_ptr<Command>> redoStack_;
};
// ── 사용 예시 ─────────────────────────────────────────────────────────────────
int main() {
Document doc;
CommandHistory history;
auto print = [&] {
std::cout << " 문서: \"" << doc.getContent() << "\"\n";
};
// 텍스트 삽입
history.execute(std::make_shared<InsertCommand>(doc, 0, "Hello"));
print();
history.execute(std::make_shared<InsertCommand>(doc, 5, ", World!"));
print();
// 텍스트 삭제
history.execute(std::make_shared<EraseCommand>(doc, 5, 7)); // ", World" 삭제
print();
// 매크로 커맨드: 여러 편집을 하나의 원자 단위로
auto macro = std::make_shared<MacroCommand>();
macro->add(std::make_shared<InsertCommand>(doc, 5, " C++"));
macro->add(std::make_shared<InsertCommand>(doc, 9, "17"));
history.execute(macro);
print();
// 취소 (undo) 3회
std::cout << "\n--- 취소 ---\n";
history.undo(); print();
history.undo(); print();
history.undo(); print();
// 재실행 (redo) 2회
std::cout << "\n--- 재실행 ---\n";
history.redo(); print();
history.redo(); print();
}