design patterns · medium
GoF Command — Requests as Objects: Undo, Redo, Macro, Queue
The Command pattern encapsulates a request as an object with execute() and undo() methods. ConcreteCommands (InsertCommand, DeleteCommand) hold the Receiver (Document) and all parameters needed to reverse the action (position, text). The Invoker (CommandManager) calls cmd->execute(), pushes to an undo_stack, and clears the redo_stack on new actions. Undo: pop from undo_stack → cmd->undo() → push to redo_stack. Redo: pop from redo_stack → cmd->execute() → push to undo_stack. The same Command object travels between stacks without re-creation. Macro recording is free: a vector<Command*> — record = push, replay = iterate + execute. Additional capabilities from the same abstraction: audit log (persist before execute), job queue (commands as work items), remote execution (serialize + send).
Command: turn each request into an object with execute() + undo(); Invoker stores them on an undo_stack — call undo() to reverse; push to redo_stack to re-apply; same abstraction gives macro, log, queue, and remote execution for free.
The code
// Command interface — the key abstractionclass Command {public: virtual void execute() = 0; virtual void undo() = 0; virtual ~Command() = default;};
// ConcreteCommand — encapsulates a request as an objectclass InsertCommand : public Command { Document& doc_; string text_; int pos_;
public: InsertCommand(Document& d, string t, int p) : doc_(d), text_(t), pos_(p) {} void execute() override { doc_.insert(pos_, text_); } void undo() override { doc_.erase(pos_, text_.size()); }};class DeleteCommand : public Command { Document& doc_; string deleted_; int pos_; int count_; void execute() override { deleted_ = doc_.substr(pos_, count_); doc_.erase(pos_, count_); } void undo() override { doc_.insert(pos_, deleted_); }};
// Invoker — tracks history; decoupled from what commands doclass CommandManager { stack<unique_ptr<Command>> undo_stack_; stack<unique_ptr<Command>> redo_stack_;
public: void execute(unique_ptr<Command> cmd) { cmd->execute(); undo_stack_.push(move(cmd)); while (!redo_stack_.empty()) redo_stack_.pop(); // clear redo on new action } void undo() { if (undo_stack_.empty()) return; auto cmd = move(undo_stack_.top()); undo_stack_.pop(); cmd->undo(); redo_stack_.push(move(cmd)); } void redo() { if (redo_stack_.empty()) return; auto cmd = move(redo_stack_.top()); redo_stack_.pop(); cmd->execute(); undo_stack_.push(move(cmd)); }};What this lesson walks through
- 01Why Command? — undo requires remembering the past
- 02Execute InsertCommand('Hello') — pushed to undo stack
- 03Execute InsertCommand('World') — stack grows
- 04Ctrl+Z — undo pops top command, pushes to redo
- 05Ctrl+Y — redo re-executes from redo stack
- 06Macro: record a sequence of commands, replay on demand
If the Invoker calls receiver.insertText() directly, there is no record of what was done. You can't undo. You can't replay. You can't queue commands for a remote server. Command pattern solves this by turning each request into an object with execute() and undo() — the object is stored for later.
See it animated — step by step, at your own pace
Unlock the full interactive walkthrough of GoF Command — Requests as Objects: Undo, Redo, Macro, Queue and 100+ animated C++ interview lessons.