Skip to content

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 --> TextEditor

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

idle
Click Run TS to execute. First run downloads Babel (~400 KB, cached after that).

Tradeoffs

ProCon
Undo/redo is a natural consequenceEach operation needs its own class
Commands are first-class: queue, log, serializeDeleted state must be captured at execute time
Decouples sender from receiverHistory can consume significant memory
Composable into macro commandsRedo 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 of execute().
  • Truncate redo history on new commands: when the user makes a new edit after undoing, any redoable future is gone. Truncate history to cursor + 1 before pushing the new command.
  • Python interface idiom: abc.ABC with @abstractmethod is the right way to define the Command interface. 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 command interface 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 Command interface. No change needed in CommandHistory.

References

  • 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