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).

🔑 Key line

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 abstraction
class Command {
public:
virtual void execute() = 0;
virtual void undo() = 0;
virtual ~Command() = default;
};
// ConcreteCommand — encapsulates a request as an object
class 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 do
class 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

  1. 01Why Command? — undo requires remembering the past
  2. 02Execute InsertCommand('Hello') — pushed to undo stack
  3. 03Execute InsertCommand('World') — stack grows
  4. 04Ctrl+Z — undo pops top command, pushes to redo
  5. 05Ctrl+Y — redo re-executes from redo stack
  6. 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.

← Previous
GoF Strategy — Pluggable Interchangeable Algorithms
Next →
State Pattern