Command
The button directly calls light.turn_on().
Wrap each action in a small object with execute() (and undo()).
You need undo / redo
The picture from life
A waiter writes your order on a slip and hands it to the kitchen. The slip is the request in written form. It can wait in a queue, be handed to another cook, be cancelled, or be kept in a register for the bill. You can do none of this with a shout across the room.
The problem
What goes wrong when you do not use it.
The button directly calls light.turn_on(). You cannot undo it, queue it, log it or bind the same button to something else. The action and the thing that triggers it are glued together.
- calls / uses
- You need undo / redo
- Actions should be queued, scheduled, logged or replayed
- A button, menu and shortcut should trigger the same action
The idea
How the pattern fixes it.
Wrap each action in a small object with execute() (and undo()). The sender holds commands, not receivers. Keep a history stack to undo.
- implements
- calls / uses
Who is who
- Command
- execute() and undo().
- TurnOn, TurnOff
- Concrete commands, each holds the Light.
- Light (receiver)
- Does the real work.
- Remote (invoker)
- Runs commands and keeps the history for undo.
Build it in steps
- 1Define a Command interface with execute and undo.
- 2Write one command per action holding the receiver.
- 3The invoker runs a command and pushes it on a stack.
- 4Undo pops the stack and calls undo().
Watch it run
Step through the calls. Tap an arrow to jump to it.
press(TurnOn(light)): the remote runs the command object.
execute()The code
Python and Java. Try writing the classes yourself from the diagram first.
class Light:
def __init__(self):
self.on = False
class TurnOn:
def __init__(self, light):
self.light = light
def execute(self):
self.light.on = True
def undo(self):
self.light.on = False
class TurnOff:
def __init__(self, light):
self.light = light
def execute(self):
self.light.on = False
def undo(self):
self.light.on = True
class Remote:
def __init__(self):
self.history = []
def press(self, command):
command.execute()
self.history.append(command)
def undo(self):
if self.history:
self.history.pop().undo()
def demo():
light, remote, states = Light(), Remote(), []
remote.press(TurnOn(light)); states.append(light.on)
remote.press(TurnOff(light)); states.append(light.on)
remote.undo(); states.append(light.on)
return statesWhat you gain
- Undo / redo, queues, logs and macros come naturally
- Sender and receiver are decoupled
What it costs
- One small class per action
- Undo needs the command to remember enough state
You have already met it in
- Editor undo stacks (Ctrl+Z)
- Java's Runnable passed to an executor
- Database transaction logs and job queues
Often mixed up with
Make it stick
Build a text editor with Type and Delete commands and an undo stack. Add redo with a second stack.