Design Patterns Command

The Command pattern turns a request into a standalone object. That object contains everything needed to carry out the action: what to do, who does it, and what data to use. You can store this object, queue it, pass it around, or reverse it.

Think of a restaurant. You (the client) tell the waiter (the invoker) your order. The waiter writes it on a ticket (the command object) and hands it to the kitchen (the receiver). The kitchen executes the order. The waiter does not cook. The ticket is the command.

What Problem Does It Solve?

Without Command, a button directly calls a method:

button.onClick(() -> textEditor.bold());

This works for one action. But what if you need undo? What if you want to record every action the user took and replay it? What if actions should queue up and execute later? A direct call cannot support any of that. Command solves all three.

Core Roles in Command

Command Interface

Declares two methods: execute() to perform the action and undo() to reverse it.

Concrete Command

Implements the interface. Holds a reference to the Receiver and calls the right method on it.

Receiver

The object that knows how to perform the actual work. A text editor, a file system, a database — whatever does the real job.

Invoker

Stores and triggers commands. It does not know what the command does — it just calls execute().

Client

Creates the command objects, sets up the receiver, and gives the command to the invoker.

A Diagram in Plain Text

+--------+     creates      +------------------+     calls    +----------+
| Client |----------------> | ConcreteCommand  | -----------> | Receiver |
+--------+                  |                  |              |          |
                            |  execute()       |              | action() |
    |                       |  undo()          |              +----------+
    | gives to              +------------------+
    v
+----------+     triggers
| Invoker  | -----------------> command.execute()
|          |
| history[]|  (stores commands for undo)
+----------+

Code Example: Text Editor with Undo

Step 1 — The Receiver

class TextEditor {
    private StringBuilder text = new StringBuilder();

    public void insertText(String content) {
        text.append(content);
        System.out.println("Text now: " + text);
    }

    public void deleteText(int length) {
        int start = text.length() - length;
        text.delete(start, text.length());
        System.out.println("Text now: " + text);
    }

    public String getText() {
        return text.toString();
    }
}

Step 2 — The Command Interface

interface Command {
    void execute();
    void undo();
}

Step 3 — A Concrete Command

class InsertTextCommand implements Command {
    private TextEditor editor;
    private String content;

    public InsertTextCommand(TextEditor editor, String content) {
        this.editor = editor;
        this.content = content;
    }

    public void execute() {
        editor.insertText(content);
    }

    public void undo() {
        editor.deleteText(content.length());
    }
}

Step 4 — The Invoker with Undo History

class EditorHistory {
    private Stack<Command> history = new Stack<>();

    public void executeCommand(Command cmd) {
        cmd.execute();
        history.push(cmd);
    }

    public void undo() {
        if (!history.isEmpty()) {
            Command last = history.pop();
            last.undo();
        }
    }
}

Step 5 — Wire It Together

TextEditor editor = new TextEditor();
EditorHistory history = new EditorHistory();

history.executeCommand(new InsertTextCommand(editor, "Hello "));
history.executeCommand(new InsertTextCommand(editor, "World"));
// Text now: Hello World

history.undo();
// Text now: Hello

history.undo();
// Text now: (empty)

Command Queue: Executing Actions Later

Commands are objects, so you can store them in a queue and run them at the right moment. This is useful for scheduling, rate-limiting, or batching operations.

Queue<Command> queue = new LinkedList<>();

// Add commands without running them yet
queue.add(new InsertTextCommand(editor, "Line 1\n"));
queue.add(new InsertTextCommand(editor, "Line 2\n"));
queue.add(new InsertTextCommand(editor, "Line 3\n"));

// Run all at once
while (!queue.isEmpty()) {
    queue.poll().execute();
}

Macro Commands

A macro command groups multiple commands into one. When you call execute() on the macro, it calls execute() on each child in sequence.

class MacroCommand implements Command {
    private List<Command> commands = new ArrayList<>();

    public void add(Command cmd) {
        commands.add(cmd);
    }

    public void execute() {
        for (Command cmd : commands) {
            cmd.execute();
        }
    }

    public void undo() {
        // Undo in reverse order
        for (int i = commands.size() - 1; i >= 0; i--) {
            commands.get(i).undo();
        }
    }
}

Real-World Uses

  • Undo/Redo in editors: Photoshop, Word, and IDEs store every action as a command and replay or reverse them on demand.
  • Transaction systems: A banking transfer consists of debit and credit commands. If one fails, the other rolls back.
  • Remote controls: Each button press creates a command object. Macros record multiple presses as a single command.
  • Job queues: Background workers pull command objects from a queue and execute them asynchronously.
  • GUI toolbars: Every toolbar button holds a command. Disabling the button is as simple as setting the command to null.

Common Mistakes to Avoid

Not Implementing Undo When You Need It

If undo is a requirement, design the receiver to support reversible operations from the start. Retrofitting undo into a non-reversible receiver is painful. Plan for it early.

Putting Business Logic in the Command

Commands should delegate to the receiver, not contain logic themselves. A command that does its own database queries or calculations becomes hard to reuse and test.

Ignoring Command Serialization

If you need to persist commands (replay audit logs, send commands over a network), your command objects must serialize cleanly. Avoid storing live object references inside commands meant for serialization.

Command vs Strategy

Command:
  - Encapsulates a REQUEST (an action + its data)
  - Supports undo, queuing, logging
  - The invoker does not know what the command does

Strategy:
  - Encapsulates an ALGORITHM (how to do a calculation)
  - Swapped to change behavior
  - The context calls the strategy directly with data

Use Command when you need to record, queue, or reverse actions. Use Strategy when you need to swap algorithms at runtime.

Benefits and Trade-offs

Benefits

  • Undo and redo support comes naturally from stored command history.
  • Commands can queue, schedule, and replay in any order.
  • Invoker and receiver are fully decoupled.
  • New commands can be added without changing the invoker.

Trade-offs

  • More classes for each action increases the file count.
  • Long undo histories consume memory — you may need a limit.
  • Complex undo logic for some operations (like database updates) requires careful design.

Quick Summary

Command wraps a request as an object. The invoker stores and triggers commands without knowing what they do. The receiver performs the actual work. This separation enables undo, redo, queuing, and logging — four capabilities that are nearly impossible to add to direct method calls cleanly.

Leave a Comment