LESSON 6.4
Command
Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.
15 min read
Real-world analogy
A restaurant order slip exemplifies the Command pattern. When you order lunch, the waiter does not drag you into the kitchen to shout instructions at the chef. Instead, the waiter writes your order onto a paper order slip. That order slip is a command object: it encapsulates the receiver (the kitchen), the parameters (medium-rare steak, no onions), and the action. It can sit in a queue on the kitchen spindle, be prioritized, logged for billing, or torn up if you cancel before the stove is lit.
The problem
You are building a rich text editor. The application contains multiple user interface entry points that perform identical operations:
- Saving a document can be triggered by:
- Clicking the toolbar "Save" floppy disk icon.
- Selecting "File > Save" in the dropdown menu.
- Pressing
Ctrl+Son the keyboard.
If each UI widget (Toolbar Button, Menu Item, Keyboard Shortcut Listener) communicates directly with the underlying Document and FileSystem classes, business logic is duplicated across all controls. Furthermore, supporting multi-level Undo and Redo becomes impossible because buttons do not retain the previous state of the text they altered:
// Naive implementation: Direct coupling between UI controls and business logic
class SaveButton {
#document;
constructor(document) { this.#document = document; }
onClick() {
// Directly invoking business logic from UI button
this.#document.writeToDisk();
}
}
class KeyShortcutListener {
#document;
constructor(document) { this.#document = document; }
onCtrlS() {
// Duplicate invocation logic across UI triggers
this.#document.writeToDisk();
}
}public class Example {
static class Document {
public void writeToDisk() { System.out.println("Saving to disk..."); }
}
static class SaveButton {
private final Document doc;
public SaveButton(Document doc) { this.doc = doc; }
Hardwiring UI triggers directly to domain receivers prevents queueing actions, logging command history for crash recovery, or reversing operations.
The solution
The Command pattern converts a request into a standalone object that encapsulates all details of the call: the target receiver, the method to invoke, and its arguments. This decoupling allows invokers (buttons, menus, shortcuts) to execute any command uniformly without knowing what receiver is being modified.
- Command (
Command): Interface declaringexecute()andundo(). - Concrete Commands (
InsertTextCommand,DeleteTextCommand): Binds an action to a specificReceiverand stores parameters and prior state. - Receiver (
TextDocument): The domain class that actually carries out the work. - Invoker (
EditorHistory/ToolbarButton): Triggers the command and maintains undo/redo history stacks.
Step-by-step implementation
Step 1: Define the command contract
Declare the core execution and reversible undo contracts.
class Command {
execute() {
throw new Error("execute() must be implemented");
}
undo() {
throw new Error("undo() must be implemented");
}
}public class Example {
interface Command {
Step 2: Implement the receiver
The receiver holds the actual business state and executes concrete mutations.
Step 3: Implement concrete commands with state capture
The concrete command captures the parameters and state necessary to reverse the operation during undo().
The complete example
Here is the complete, runnable implementation demonstrating an editor with full multi-level Undo and Redo stacks.
--- Typing text ---
Current Document: "Hello World!"
--- Triggering Undo twice ---
After 1st Undo: "Hello "
After 2nd Undo: ""
--- Triggering Redo once ---
After Redo: "Hello "Variations
Macro commands (composite commands)
A Macro Command applies the Composite pattern to the Command pattern. A single macro command holds an array of child Command objects. When macro.execute() is called, it iterates through each sub-command and executes them in sequence. When macro.undo() is called, it executes the reverse operations in reverse order. This enables features like Photoshop Action recording or keyboard macros.
Asynchronous job queues and WAL logging
Because commands are standalone serializable objects:
- Job Queues: Commands can be pushed into a persistent queue (e.g. BullMQ, Celery, SQS). Worker processes pop commands and invoke
execute()asynchronously without knowing the original requester. - Write-Ahead Logging (WAL): Commands can be serialized to disk before execution. In the event of a system crash, the database replays the command log to reconstruct consistent state.
Where you've already used it
java.lang.Runnable&java.util.concurrent.Callable: Thread executors parameterize background worker pools with executable tasks encapsulated as commands.- Redux Actions: Action objects
{ type: 'ADD_TODO', payload: 'Buy Milk' }encapsulate mutations dispatched to reducers. - Database Write-Ahead Logging: SQL mutation commands are serialized to transaction logs before committing to tables.
- CQRS (Command Query Responsibility Segregation): Explicitly separates write commands (which modify state without returning values) from queries (which read state without side effects).
Pros and cons
Advantages
- Single Responsibility Principle: Isolates the operations that invoke actions from the classes that execute the actions.
- Open/Closed Principle: Add new commands without altering existing invokers or receivers.
- Enables Undo / Redo: Provides a natural architecture for history tracking and rollback.
- Enables Deferred & Remote Execution: Commands can be scheduled, delayed, queued, or serialized over network sockets.
Disadvantages
- Class Proliferation: Can introduce dozens of small command classes for every minor operation in the application.
- State Complexity: Capturing the complete delta required for
undo()can consume significant memory for complex mutations.
When not to use it
Do not use Command if operations are simple, synchronous, and never require undo, redo, queueing, or logging. Invoking receiver methods directly is much simpler than creating an entire command hierarchy.
Related patterns
| Pattern | Focus | Primary Objective |
|---|---|---|
| Command | Action as an object | Parameterization, queuing, and undo/redo |
| Strategy | Interchangeable algorithm | Swapping execution algorithms inside a context |
| Memento | State snapshot | Saving and restoring internal state without violating encapsulation |
| Composite | Macro execution | Used to implement Composite/Macro commands |
Command: editor with undo/redo
Encapsulate text editing actions into Command objects supporting execute(), undo(), and multi-level undo/redo history.
Click Run tests to evaluate your solution against the behavior and structural test suite.
Interview questions
How does the Command pattern differ from the Strategy pattern? Command encapsulates an action or request as a standalone object that can be queued, logged, or undone. Strategy encapsulates an interchangeable algorithm for how to accomplish a specific task within a context object.
How is Redo implemented alongside Undo in the Command pattern?
The invoker maintains two stacks: an Undo stack and a Redo stack. When an action executes, it is pushed to Undo and Redo is cleared. Calling undo() pops the command from Undo, calls cmd.undo(), and pushes it to Redo. Calling redo() pops from Redo, calls cmd.execute(), and pushes back to Undo.
How does the Command pattern support transaction rollbacks?
Commands can implement a two-phase commit: execute() checks invariants and stages changes, while undo() (or rollback()) reverts staged modifications if any step in the transaction fails.
What is the difference between Command and Memento in implementing Undo?
Command implements undo algorithmically by running the inverse operation (e.g. deleteLast to undo insert). Memento implements undo by taking a snapshot of the object's entire previous state and restoring it directly.
Try it
Undo/Redo Editor LLD
Model reversible commands, history branches and atomic grouped edits.