Behavioral pattern
Command
Wrap a request as an object, instead of a direct method call — so an invoker can execute, queue, log, or undo it without knowing how it works.
✗ The problem
The button IS the action
The toolbar calls the receiver directly. There's no object representing "this request" — so nothing to undo, queue, log, or replay.
button.onclick = () => {
// invoker knows the receiver AND the action
editor.value += 'x';
};
// want undo? a macro? a log?
// …you'd have to hand-edit every button.
The invoker is welded to what it does. Add a feature like undo, and there's no
request object to hang it on.
✓ The pattern
↓
↓
Encapsulate the request as an object
A Command holds execute() / undo(). The invoker only
ever calls that contract — never the receiver directly.
class AddTextCommand {
constructor(editor, text) {
this.editor = editor;
this.text = text;
}
execute() {
this.editor.value += this.text;
}
undo() {
this.editor.value = this.editor.value
.slice(0, -this.text.length);
}
}
Invoker
history stack
Command
execute() / undo()
Receiver
editor
✓ See it live
Every click becomes an undoable command
Each button builds a command and calls execute(). The invoker pushes it onto a
history stack — Undo pops the stack and calls undo().
📝 Editor (receiver)
📚 History (invoker's stack)
✓ Takeaway
Requests become first-class objects
- Decouples the invoker (button) from the receiver (editor) — the invoker only calls
execute(). - A request is now an object → enables undo/redo, queues, macros, logging, retries.
- You already use it: DB transactions, task/job queues, editor undo history, GUI menu actions.
- Caution: a class per trivial action adds boilerplate — a plain closure is often enough.
🎯 Principle applied: Command applies SRP (each action is its own object), Open/Closed (add commands without changing the invoker), and Dependency Inversion (the invoker depends on the
Command interface).Back to all topics →