Home Design Patterns Command
Command
Behavioral Gang of Four“Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.”
Gamma, Helm, Johnson & Vlissides, p. 233Purpose
A Command turns a method call into a thing. Instead of the caller invoking
arm.moveTo(pose) directly, it builds an object that holds the
receiver, the arguments and the knowledge of what to do with them, and hands that
object to somebody else to run.
Everything the pattern is good for follows from that one move. A call cannot be put in a queue, written to disk, sent across a network, retried after a power cut, or reversed. An object can be all of those. Undo and job queues are not two separate applications of Command; they are the same trick applied to time in two directions.
Design
Four roles. The Command declares one operation, conventionally
execute(). A ConcreteCommand holds a reference to a Receiver and
whatever arguments the operation needs, and implements execute() by
calling the receiver. The Invoker holds commands and runs them, knowing nothing
about what they do. The Client wires a concrete command to a receiver and gives
it to the invoker.
Undo is where the design decisions live. A command can undo itself by applying an inverse operation, which is cheap but only exists for operations that have one, or by recording enough of the receiver's prior state to put it back, which always works and costs memory. The second option is a Memento living inside a Command, and that pairing is so common it is worth treating as a single technique.
Example
Autobot Robotics' task scheduler originally called the arm controller directly from the operator interface. A shift supervisor picked a program, the UI thread walked it, and each step was a method call. It worked until three separate requirements arrived within a year and none of them could be met.
The first was recovery. When a cell lost power mid-batch, nobody could tell what had run. The second was the teach pendant, where an operator jogs the arm to build a program and wants to step backwards when a waypoint is wrong. The third was office-side scheduling, where a planner queues work for a cell that is currently busy, possibly from a different building. A method call satisfies none of these, because by the time you want to ask about it, it is gone.
Making each operation a command object solved all three with one change. The
queue became a list of objects that could be written to the local database and
re-read after a restart, so recovery was a matter of asking which commands had
been marked done. Stepping backwards on the pendant became a stack of executed
commands with an undo(). Remote scheduling became serializing a
command and posting it. It is worth saying plainly that undo is only honest for
the jog moves and the program edits: you cannot un-drill a hole, and the team
deliberately refused to offer undo on anything that had touched a workpiece.
interface Command {
void execute();
void undo();
}
class MoveToCommand : Command {
private ArmController arm;
private Pose target;
private Pose previous;
public MoveToCommand(ArmController a, Pose t) { arm = a; target = t; }
public void execute() {
previous = arm.currentPose();
arm.moveTo(target);
}
public void undo() {
arm.moveTo(previous);
}
}
class OpenGripperCommand : Command { } // same shape
class RunProgramCommand : Command { } // holds a list of Commands
class Scheduler {
private Queue<Command> pending;
private Stack<Command> done;
public void submit(Command c) { pending.Enqueue(c); }
public void run() {
while (pending.Count() > 0) {
Command c = pending.Dequeue();
c.execute();
done.Push(c);
}
}
public void stepBack() {
if (done.Count() > 0) done.Pop().undo();
}
}
previous =
arm.currentPose() inside execute(), which is what makes
undo() possible at all, and Queue<Command>,
which is a schedule of work the scheduler cannot interpret.Seen in the Wild
WPF's ICommand is the pattern under its own name, with buttons
and menu items bound to command objects that also report whether they are
currently enabled. Qt ships QUndoCommand and
QUndoStack, and Java Swing ships Action alongside
UndoableEdit and UndoManager, both explicitly for
undo. Every task queue works this way too: a
Runnable handed to a Java ExecutorService, a Celery
task, or a Sidekiq job is a serialized command waiting for an invoker. A database
write-ahead log is the same idea at the storage layer, which is why replaying one
restores a database.
Comparisons
- Memento is Command's usual partner. The command says what happened; the memento says what the world looked like before it did.
- Strategy also puts behavior in an object, but a Strategy is a parameter to an algorithm that runs now. A Command is a request that has a lifetime, and often outlives the code that created it.
- Composite gives you macro commands. A command holding a list of commands is itself a command, and that is how a recorded macro or a transaction gets built.
- Chain of Responsibility answers who should handle a request; Command answers what the request is. They combine well.
- Prototype matters if commands must be logged: the log needs a copy of the command as it was, not a reference to one that has since been reused.
Why It Exists
My take
Command exists because a function call is not a value. It happens at one moment, in one thread, on one machine, and leaves nothing behind you can point at. Every requirement that involves the word "later" collides with that: queue it for later, retry it later, undo it later, audit later what was done. The pattern's whole content is that if you want to do something to a request, the request has to exist first.
Once you see it that way, you notice it everywhere in infrastructure rather than in application code. A message on a queue is a command. A row in a job table is a command. A write-ahead log record is a command, and crash recovery is an invoker replaying commands it does not understand. None of those authors were thinking about a Gang of Four pattern; they arrived at the same place because reifying the verb is the only way to survive a process boundary or a power cut.
The honest cost is that undo is much harder than the catalog makes it sound. Undo works when the receiver's state is fully yours and fully in memory. It gets awkward the moment a command sent an email, took a lock, moved a physical object, or committed a transaction. I have seen more damage done by an undo stack that was believed than by not having one, because an undo that silently only restores half the state is worse than a refusal. Be specific about which commands are reversible and make the rest say no.
Criticisms
It generates classes at an alarming rate. Every operation the user can invoke becomes a type, with a constructor that duplicates the method signature you were trying to avoid writing twice. For an editor with two hundred menu items, that is two hundred classes whose bodies are one line each. Modern languages have absorbed the simple case: a lambda, a delegate or a closure is a command object with no ceremony, and if you do not need undo or serialization you should probably use one.
Commands also tend to accumulate dependencies. The first version holds a receiver. Then one needs a logger, one needs the current user for a permission check, one needs the unit of work, and now constructing a command means having half the application available. This is the point at which people bolt a service locator onto it, which trades an explicit dependency for a hidden one and makes the commands untestable in the process.
Finally, serialized commands are a versioning problem waiting to happen. A queue that survives a restart holds commands written by the previous version of your software, and the class they deserialize into has since changed. Anyone who has upgraded a system with a non-empty job queue knows this, and the pattern offers no help with it at all.