Command Pattern
The problem
In most applications, user actions and the code that handles them are directly coupled. A button click calls a method. A menu item calls a different method. When you need undo, you realize every action needs a reverse operation, but those reverse operations are scattered across unrelated objects with no shared structure. Logging, queueing, and replaying operations have the same problem: there is no object to log, queue, or replay.
The Command pattern fixes this by packaging each operation as an object with a uniform interface. Every command knows how to execute itself and how to undo itself. A history stack of command objects gives you undo and redo for free. A queue of command objects gives you deferred execution for free. Audit logging means serializing a list of command objects rather than instrumenting dozens of call sites.
Structure
classDiagram class Command { <<interface>> +execute() +undo() } class InsertCommand { -editor: TextEditor -text: string -position: number +execute() +undo() } class DeleteCommand { -editor: TextEditor -position: number -length: number -deleted: string +execute() +undo() } class CommandHistory { -history: Command[] -cursor: number +execute(cmd) +undo() +redo() } class TextEditor { -content: string +insert(text, pos) +delete(pos, len) +getContent() string } Command <|-- InsertCommand Command <|-- DeleteCommand CommandHistory --> Command InsertCommand --> TextEditor DeleteCommand --> TextEditorWhen to use
- You need undo and redo. The history stack of command objects is the canonical implementation.
- You want to queue or schedule operations rather than execute them immediately.
- You need an audit log of every action a user or system took, in order and reversible.
- You want to compose simple commands into macro commands without changing the calling code.
Implementation
TextEditor is the receiver: it owns the content and exposes insert and delete. Commands wrap operations on the editor and capture enough state to reverse them. CommandHistory manages the stack and handles redo by truncating forward history when a new command arrives. Python uses abc.ABC with @abstractmethod for the Command interface. Go uses an interface type since there are no abstract classes; note that string slicing on raw string works for this ASCII example, but production Unicode code would convert to []rune first.
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).
Click Run Python to execute. First run downloads Python (~10 MB, cached after that).
Click Run Go to execute. Runs via the Go Playground API.
Tradeoffs
| Pro | Con |
|---|---|
| Undo/redo is a natural consequence | Each operation needs its own class |
| Commands are first-class: queue, log, serialize | Deleted state must be captured at execute time |
| Decouples sender from receiver | History can consume significant memory |
| Composable into macro commands | Redo breaks if history is branching (must truncate) |
Gotchas
- Capture deleted content in
execute(), not the constructor: the constructor runs before the edit happens, so the content to be deleted does not exist yet. Capture it as the first step ofexecute(). - Truncate redo history on new commands: when the user makes a new edit after undoing, any redoable future is gone. Truncate
historytocursor + 1before pushing the new command. - Python interface idiom:
abc.ABCwith@abstractmethodis the right way to define theCommandinterface. A plain class with unimplemented methods will work but won’t fail loudly when a subclass forgets to implement one. - Go package-private interfaces: if all command types live in one package, an unexported
commandinterface is fine. If commands cross package boundaries (e.g. plugins), the interface must be exported. - Macro commands: a composite command that holds a list of sub-commands and calls each in turn implements the same
Commandinterface. No change needed inCommandHistory.
References
- Design Patterns: Elements of Reusable Object-Oriented Software, GoF, pp. 233-242, the original Command chapter
- Command Pattern, Refactoring Guru, worked examples and diagrams
- Command Pattern in Game Programming Patterns, Robert Nystrom’s treatment with undo, redo, and replay in a game context
- Undo/Redo the Right Way, Ink and Switch on command history in collaborative editing
Related topics
- Design Patterns, the full GoF catalog and pattern index
- Observer, another behavioral pattern for decoupling senders from receivers
- Strategy, also encapsulates behavior as an object, but without undo semantics