3.2 Command
The Command pattern packages a request as an object. That sounds abstract, but once "doing something" becomes an object you can pass, store, and queue, you unlock a whole set of powers: undo/redo, operation logs, macro commands, task queues, transactions.
A programmable remote is the classic metaphor: a button doesn't "turn on the light" directly — it just holds a Command and calls execute() when pressed. The same button can be rebound to any command.
Four roles
interface Command { void execute(); }
class LightOnCommand implements Command {
private Light light; // receiver
public void execute() { light.on(); }
}
// the invoker knows only the Command interface
class Button {
private Command command;
void press() { command.execute(); }
}- Command: the interface encapsulating an action.
- Concrete command: binds a receiver and implements
execute().
- Receiver: the object that does the real work (e.g.,
Light).
- Invoker: the object that triggers the command (e.g.,
Button), knowing only the interface.
Undo: Command's killer feature
Add an undo() to the command and push executed commands onto a history stack, and you have undo/redo. The lab below lets you run +1/+5 operations, then undo and redo step by step, watching the two stacks flow.
What else it enables
- Macro commands: compose a group of commands into one, executed together ("leave home" turns off lights and AC and locks the door).
- Task queues: command objects can be serialized, enqueued, and executed asynchronously — this is exactly what a Job is in a message queue.
- Transactions and replay: record the command sequence to replay or roll back.
Command's cost is "a class per action," so introduce it only when you truly need these powers; otherwise calling a method directly is simpler.