A button that calls light.turn_on() directly can only ever turn on that light. You also can't queue the action, log it, or undo it.
Command wraps a request in an object with an execute() method. The button just runs whatever command it's given, and because commands are objects, you can store them in a history, put them in a queue, or combine them into macros.
Intent#
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. (GoF)
Analogy: a restaurant order slip. You don't shout at the chef; you hand the waiter a written order. The slip can be queued, passed along, or cancelled, and the waiter doesn't need to know how to cook.
The problem: invoker welded to receiver#
Here the button calls the light directly:
class Button:
def __init__(self, light: Light):
self.light = light
def click(self) -> None:
self.light.turn_on() # this button can only ever do this one thing
Buttonknows aboutLight, so you can't reuse it for other actions.- There's nowhere to hook in queuing, logging or undo.
With Command, the button works with any action:
button.set_command(LightOnCommand(light))
button.set_command(DoorOpenCommand(door))
button.set_command(PlayMusicCommand(stereo))
Structure#
Command has five participants:
| Participant | Role | Example |
|---|---|---|
| Command | Interface with execute() (and often undo()) |
Command |
| Concrete command | Holds a receiver and the parameters; execute() calls the receiver |
LightOnCommand |
| Receiver | The object that actually does the work | Light |
| Invoker | Stores commands and triggers them, without knowing what they do | Switch, RemoteControl |
| Client | Creates commands and wires them to invokers | main |
A single button press flows through them like this:
- Client creates the command:
LightOnCommand(light). - Invoker stores it:
switch.set_command(light_on). - User triggers the invoker:
switch.press(). - Invoker calls
command.execute(). - Command calls the receiver:
light.turn_on().
Example 1: light switch#
The switch holds one command and runs it on every press:
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
# Receiver
class Light:
def turn_on(self) -> None:
print("Light: ON")
def turn_off(self) -> None:
print("Light: OFF")
# Concrete commands
class LightOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self) -> None:
self.light.turn_on()
class LightOffCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self) -> None:
self.light.turn_off()
# Invoker
class Switch:
def __init__(self) -> None:
self.command: Command | None = None
def set_command(self, command: Command) -> None:
self.command = command
def press(self) -> None:
self.command.execute() # no idea what it does
light = Light()
wall_switch = Switch()
wall_switch.set_command(LightOnCommand(light))
wall_switch.press() # Light: ON
wall_switch.set_command(LightOffCommand(light))
wall_switch.press() # Light: OFF
Example 2: a programmable remote#
The invoker holds slots of commands; any device can be bound to any slot.
class RemoteControl:
def __init__(self, slots: int = 3) -> None:
self.on_commands: list[Command | None] = [None] * slots
self.off_commands: list[Command | None] = [None] * slots
def set_command(self, slot: int, on: Command, off: Command) -> None:
self.on_commands[slot] = on
self.off_commands[slot] = off
def press_on(self, slot: int) -> None:
self.on_commands[slot].execute()
def press_off(self, slot: int) -> None:
self.off_commands[slot].execute()
remote = RemoteControl()
remote.set_command(0, TVOnCommand(tv), TVOffCommand(tv))
remote.set_command(1, FanOnCommand(fan), FanOffCommand(fan))
remote.press_on(0) # TV: ON
remote.press_on(1) # Fan: ON
remote.press_off(0) # TV: OFF
Example 3: text editor with undo and redo#
Each command knows how to reverse itself. The invoker keeps the history.
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
# Receiver
class TextEditor:
def __init__(self) -> None:
self._text = ""
def write(self, words: str) -> None:
self._text += words
def delete_last(self, length: int) -> None:
self._text = self._text[:max(0, len(self._text) - length)]
def get_text(self) -> str:
return self._text
class WriteCommand(Command):
def __init__(self, editor: TextEditor, text: str):
self.editor = editor
# The command remembers what it needs to undo itself
self.text = text
def execute(self) -> None:
self.editor.write(self.text)
def undo(self) -> None:
self.editor.delete_last(len(self.text))
# Invoker with history (lists used as stacks)
class EditorInvoker:
def __init__(self) -> None:
self.undo_stack: list[Command] = []
self.redo_stack: list[Command] = []
def run(self, c: Command) -> None:
c.execute()
self.undo_stack.append(c)
self.redo_stack.clear() # a new action invalidates redo history
def undo(self) -> None:
if not self.undo_stack:
return
c = self.undo_stack.pop()
c.undo()
self.redo_stack.append(c)
def redo(self) -> None:
if not self.redo_stack:
return
c = self.redo_stack.pop()
c.execute()
self.undo_stack.append(c)
editor = TextEditor()
invoker = EditorInvoker()
invoker.run(WriteCommand(editor, "Hello "))
invoker.run(WriteCommand(editor, "World!"))
invoker.undo() # "Hello "
invoker.redo() # "Hello World!"
print(editor.get_text())
Example 4: queued orders#
Commands can be collected now and executed later, which is the basis of job queues.
from abc import ABC, abstractmethod
class Chef: # receiver
def cook(self, dish: str) -> None:
print(f"Chef: cooking {dish}")
class Order(ABC): # command
@abstractmethod
def execute(self) -> None: ...
class DishOrder(Order):
def __init__(self, chef: Chef, dish: str):
self.chef, self.dish = chef, dish
def execute(self) -> None:
self.chef.cook(self.dish)
class Waiter: # invoker: queues commands, runs them later
def __init__(self) -> None:
self._orders: list[Order] = []
def take_order(self, order: Order) -> None:
self._orders.append(order)
def send_to_kitchen(self) -> None:
for order in self._orders:
order.execute()
self._orders.clear()
chef, waiter = Chef(), Waiter()
waiter.take_order(DishOrder(chef, "burger"))
waiter.take_order(DishOrder(chef, "pasta"))
waiter.send_to_kitchen()
# Chef: cooking burger
# Chef: cooking pasta
Example 5: macro command#
A macro is a command made of commands (a Composite of commands).
class MacroCommand(Command):
def __init__(self, commands: list[Command]):
self.commands = commands
def execute(self) -> None:
for c in self.commands:
c.execute()
party_mode = MacroCommand([
LightOnCommand(light),
TVOnCommand(tv),
StereoOnCommand(stereo),
])
party_mode.execute() # Light: ON, TV: ON, Stereo: ON
Template#
This is the minimal shape of the pattern:
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
class Receiver:
def action(self) -> None:
print("Receiver: performing action")
class ConcreteCommand(Command):
def __init__(self, receiver: Receiver):
self.receiver = receiver
def execute(self) -> None:
self.receiver.action()
class Invoker:
def __init__(self) -> None:
self.command: Command | None = None
def set_command(self, command: Command) -> None:
self.command = command
def invoke(self) -> None:
self.command.execute()
Real-world examples#
Commands appear wherever work is packaged up to run later or be reversed:
- Callables submitted to a
concurrent.futuresexecutor are commands: work packaged as objects to run later on another thread. - GUI action objects (such as Qt's
QAction): one action shared by a menu item, a toolbar button and a keyboard shortcut. - Undo/redo in editors and IDEs; each edit is a command.
- Job schedulers and queues (Celery tasks, cron-style schedulers, message-queue workers).
- Transactions and workflows: deposits and withdrawals as commands that can be logged, replayed or compensated.
- Games: player moves as commands, which also makes replays easy.
In languages with first-class functions, a simple command is often just a function or lambda (button.on_click(lambda: light.turn_on())). You need a full class when the command must carry undo(), metadata or serializable state.
When to use it#
Command pays off when you need any of these:
- You need undo/redo.
- You want to queue, schedule or retry operations, or run them on another thread.
- You need an audit log of operations.
- You want macros: combining several operations into one.
- The invoker (UI, scheduler) should be decoupled from the receiver.
When not to use it#
Skip it when:
- A direct method call does the job and you need none of queuing, logging or undo.
- The operation is one-off and never parameterized or reused.
Pitfalls#
Watch for these issues:
- Class explosion: one class per action. Use lambdas for simple commands.
- Undo is harder than it looks. The command must capture enough state to reverse itself. When reversing is hard, store a snapshot (Memento) instead.
- Memory. Long undo histories hold every command; cap the history size.
- Clear the redo stack when a new command runs, or redo will replay stale actions.
Command vs similar patterns#
Command is often confused with these patterns:
| Pattern | Difference |
|---|---|
| Strategy | Encapsulates how to do a task (an algorithm); Command encapsulates what to do (a request). "Save file" is a command; "which compression to use" is a strategy |
| Observer | Notifies many listeners of an event; Command is a one-way request to one receiver |
| Chain of Responsibility | Passes a request along until someone handles it; a command already knows its receiver |
| Memento | Stores state for restore; Command stores an operation. They're often combined for undo |
Key takeaways#
- Command turns a request into an object with
execute()(and optionallyundo()). - The invoker triggers commands without knowing what they do or who the receiver is.
- Because commands are objects, you can queue, log, combine and undo them.
- Undo/redo = two stacks of commands; clear redo on every new command.
- For simple cases, a lambda or function is a perfectly good command.