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. 233

Purpose

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.

UML class diagram: Invoker aggregates a Command interface; ConcreteCommand implements it and holds a Receiver; the Client creates the ConcreteCommand.
The Invoker's only dependency is on the Command interface. That is what lets one queue, one toolbar or one scheduler drive operations written long after it was.

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();
    }
}
Two lines carry the pattern: 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.