On this page
Description
Command is the behavioral pattern for turning a request into an object: instead of calling editor.deleteLast(5) right away, you create a DeleteCommand that knows what to do and to whom. Because the request is now a thing, it can be stored, queued, logged, undone, redone or replayed. Undo/redo, job queues, macros and CQRS all rest on this idea. This skill covers the pattern, an undo/redo history, and what changes when commands travel through queues.
A text editor has Type, Delete and Paste buttons. Each button calls the editor directly:
Java
typeButton.onClick(() -> editor.insert(text));
deleteButton.onClick(() -> editor.deleteLast(5));
Python
type_button.on_click(lambda: editor.insert(text))
delete_button.on_click(lambda: editor.delete_last(5))
Now users want Undo and Redo, a keyboard shortcut for every action, and recordable macros. None of that is possible, because the actions happen immediately and leave nothing behind to undo, replay or queue.
We'll go from simple to more technical:
- The pattern: commands, receivers and invokers.
- Undo and redo with two stacks, step by step.
- Where you'll see commands: executors, UI actions, job queues, CQRS.
- What undo needs to remember, and the rules for commands that are queued or retried.
The pattern
Command turns a request into an object. The object knows what to do and to whom (the receiver), so it can be executed later, stored in a history, undone, or replayed.
Java
interface Command {
void execute();
void undo();
}
class DeleteCommand implements Command {
private final Editor editor;
private final int count;
private String deleted; // remembered so undo can restore it
DeleteCommand(Editor editor, int count) { this.editor = editor; this.count = count; }
public void execute() { deleted = editor.deleteLast(count); }
public void undo() { editor.insert(deleted); }
}
class History {
private final Deque<Command> undoStack = new ArrayDeque<>();
private final Deque<Command> redoStack = new ArrayDeque<>();
void run(Command c) { c.execute(); undoStack.push(c); redoStack.clear(); }
void undo() { if (!undoStack.isEmpty()) { Command c = undoStack.pop(); c.undo(); redoStack.push(c); } }
void redo() { if (!redoStack.isEmpty()) { Command c = redoStack.pop(); c.execute(); undoStack.push(c); } }
}
Python
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self) -> None: ...
@abstractmethod
def undo(self) -> None: ...
class DeleteCommand(Command):
def __init__(self, editor: Editor, count: int):
self._editor = editor
self._count = count
self._deleted = "" # remembered so undo can restore it
def execute(self) -> None:
self._deleted = self._editor.delete_last(self._count)
def undo(self) -> None:
self._editor.insert(self._deleted)
class History:
def __init__(self):
self._undo: list[Command] = [] # a list is Python's stack: append / pop
self._redo: list[Command] = []
def run(self, c: Command) -> None:
c.execute()
self._undo.append(c)
self._redo.clear()
def undo(self) -> None:
if self._undo:
c = self._undo.pop()
c.undo()
self._redo.append(c)
def redo(self) -> None:
if self._redo:
c = self._redo.pop()
c.execute()
self._undo.append(c)
How it works, step by step
- Define a
Commandinterface:execute()and, if you need it,undo(). - Each concrete command stores its receiver and parameters, plus anything needed to undo.
- An invoker (a button, a history, a job queue) calls
execute()without knowing what the command does. - For undo/redo, keep two stacks: executing a new command pushes it on the undo stack and clears the redo stack.
- A macro is just a command that holds a list of commands.
Where you'll see it
RunnableandCallablehanded to anExecutorServiceare commands waiting to be run - in Python, functions andfunctools.partialobjects handed toconcurrent.futures, or tasks queued in Celery.- UI frameworks' action objects (Swing
Action) bind one command to a button, a menu item and a shortcut. - Job queues and schedulers store commands (as serialized messages) to run later or retry.
- CQRS architectures model every write as a command object (
PlaceOrder,CancelOrder).
Choosing among the behavioral patterns
| If you need to... | Reach for | It's about |
|---|---|---|
| Let many objects react when something happens, without the source knowing them | Observer | Who gets told about a change. |
| Swap one algorithm for another at runtime | Strategy | How a task is done - chosen from outside. |
| Turn a request into an object you can queue, log, undo or redo | Command (this lesson) | What to do, packaged for later. |
| Make an object behave differently depending on its current state | State | Behavior that changes itself as the object moves between states. |
| Fix the steps of an algorithm but let subclasses fill in some of them | Template Method | The skeleton stays, the details vary. |
Common mistakes
| Mistake | What happens | Do this instead |
|---|---|---|
| A command that doesn't remember what it changed | undo can't restore it | store the old value or deleted text in execute() |
| Not clearing the redo stack on a new command | redo recreates a history that never happened | clear redo whenever a new command runs |
| Recording commands that failed | undo reverses something that never happened | push to history only after success |
| An unbounded history | memory grows forever | cap it (drop the oldest) |
| Queuing commands that aren't idempotent | a redelivered command charges twice | a unique command id the receiver checks |
Check yourself
Try to answer before opening each one - recalling beats re-reading.
1. What must a DeleteCommand remember so that undo() works?
Show answer
The exact text it deleted - the count alone can't say which characters were there, and fewer may have been deleted if the text was shorter.
2. You undo twice, then run a new command. What happens to the redo history?
Show answer
It is cleared. Redoing the old commands on top of the new one would recreate a history that never happened.
3. Why must commands sent through a job queue be idempotent?
Show answer
Queues redeliver: after a crash or a timeout the same command may run twice. A unique command id that the receiver records lets it ignore the repeat.
Key takeaways
- A command is a request turned into an object: it can be executed later, queued, logged, undone or replayed.
- To undo, a command must remember what it changed.
- A new command clears the redo stack; failed commands never enter the history.
- Queued commands should be serializable and idempotent.
- A command is a request that may be rejected; an event is a fact that already happened.