Command
Turn a request into an object — so it can be queued, logged, retried, and, best of all, undone.
Big idea
Command wraps an action and its arguments in an object with an execute() method (and often undo()). Once a request is an object, you can store it, pass it around, queue it, log it, replay it, or reverse it — none of which you can do with a plain method call that's already happened.
The problem it solves
A spreadsheet has toolbar buttons, keyboard shortcuts, a right-click menu, and a macro recorder, all able to "set cell B2 to 40". If each calls sheet.cells["B2"] = 40 directly:
- Undo is impossible — nobody remembered the old value.
- The same action is coded three times in three UI places.
- Macros and audit logs have nothing to record; the action left no trace.
Think of it like
A restaurant order ticket. The waiter writes your order on a ticket and pins it on the rail; the cook picks it up later. The ticket can wait in a queue, be handed to another cook, be cancelled, and at the end of the night the stack of tickets is a record of everything that happened. The waiter never cooks and the cook never takes orders.
Structure
- Command — the interface (
execute,undo). - Concrete command (
SetCell) — stores the receiver and arguments, and enough to reverse itself. - Receiver (
Sheet) — the object that does the real work. - Invoker (
History, a button, a queue) — triggers commands without knowing what they do.
In code
class Sheet:
def __init__(self):
self.cells = {}
class SetCell:
def __init__(self, sheet, cell, value):
self.sheet, self.cell, self.value = sheet, cell, value
self.old = None
def execute(self):
self.old = self.sheet.cells.get(self.cell)
self.sheet.cells[self.cell] = self.value
def undo(self):
if self.old is None:
del self.sheet.cells[self.cell]
else:
self.sheet.cells[self.cell] = self.old
def __str__(self):
return f"set {self.cell}={self.value}"
class History: # the invoker
def __init__(self):
self.done, self.undone = [], []
def run(self, cmd):
cmd.execute()
self.done.append(cmd)
self.undone.clear() # a new action ends the redo trail
def undo(self):
cmd = self.done.pop(); cmd.undo(); self.undone.append(cmd)
def redo(self):
cmd = self.undone.pop(); cmd.execute(); self.done.append(cmd)
sheet, history = Sheet(), History()
history.run(SetCell(sheet, "A1", 10))
history.run(SetCell(sheet, "B2", 40))
history.run(SetCell(sheet, "B2", 45))
print("now: ", sheet.cells)
history.undo()
print("undo: ", sheet.cells)
history.undo()
print("undo: ", sheet.cells)
history.redo()
print("redo: ", sheet.cells)
print("log: ", [str(c) for c in history.done])output
now: {'A1': 10, 'B2': 45}
undo: {'A1': 10, 'B2': 40}
undo: {'A1': 10}
redo: {'A1': 10, 'B2': 40}
log: ['set A1=10', 'set B2=40']Run and edit it in the app.
The button, the shortcut, and the macro recorder can all create a SetCell and hand it to History; none of them knows how undo works.
class Add:
def __init__(self, box, n): self.box, self.n = box, n
def execute(self): self.box.append(self.n)
def undo(self): self.box.pop()
box, done = [], []
for n in [1, 2, 3]:
c = Add(box, n); c.execute(); done.append(c)
done.pop().undo()
c = Add(box, 9); c.execute(); done.append(c)
done.pop().undo()
done.pop().undo()
print(box)What does it print? Show the answer
[1]Run and edit it in the app.
Watch it
Beyond undo
- Queues and workers — a job queue is a list of commands; workers call
execute(). - Retries — a failed command object can simply be executed again.
- Audit and replay — log commands to rebuild state (event sourcing at system scale).
- Macros — a composite command that executes a list of commands (and undoes them in reverse).
What must a command store to support undo?
When to use it — and when not to
Use it for undo/redo, queued or scheduled work, retries, macros, transactional logs, or to decouple UI controls from the actions they trigger. In Python, a simple callback (lambda or function) is often enough when you only need "do this later" — reach for command objects when you need undo, metadata, or serialisation.
Pros and cons
| Pros | Cons |
|---|---|
| Undo, redo, queues, logs, retries | A class per action can add up |
| Invoker decoupled from receiver | Undo logic must be written (and tested) per command |
| Composite commands give macros | Commands holding big state make history heavy |
How it relates
- Memento is the alternative way to undo: snapshot state instead of reversing actions; commands can store mementos.
- Commands often travel along a Chain of Responsibility.
- A macro is a Composite of commands.
- Strategy also wraps behaviour in an object, but chooses how to do something; a command represents a specific request to do it.
Which is a Command rather than a Strategy?
Key terms
- Command — An object that represents a request, with execute and often undo.
- Receiver — The object that actually performs the work when a command runs.
- Invoker — Whatever triggers commands, such as a button, a queue, or a history.
- Macro — A command made of other commands, run in order and undone in reverse.