Command Pattern
Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.
Overview
The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This lets you parameterize methods with different requests, delay or queue execution, and support undoable operations.
It is the basis for undo/redo systems, job queues, macro recording, and transactional operations.
When to Use
Use the Command Pattern when:
- You need to parameterize objects with operations to execute
- You want to queue, schedule, or execute operations remotely
- You need undo/redo functionality
- You want to log changes for replay or audit purposes
- You need transactional behavior (execute all or roll back)
Solution
Python
from abc import ABC, abstractmethod
class Command(ABC):
@abstractmethod
def execute(self):
pass
@abstractmethod
def undo(self):
pass
class Light:
def __init__(self):
self.is_on = False
def turn_on(self):
self.is_on = True
print("Light is on")
def turn_off(self):
self.is_on = False
print("Light is off")
class TurnOnCommand(Command):
def __init__(self, light: Light):
self.light = light
def execute(self):
self.light.turn_on()
def undo(self):
self.light.turn_off()
# Usage
light = Light()
cmd = TurnOnCommand(light)
cmd.execute() # Light is on
cmd.undo() # Light is off
JavaScript
class Light {
constructor() {
this.isOn = false;
}
turnOn() {
this.isOn = true;
console.log("Light is on");
}
turnOff() {
this.isOn = false;
console.log("Light is off");
}
}
class TurnOnCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOn();
}
undo() {
this.light.turnOff();
}
}
// Usage
const light = new Light();
const cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo(); // Light is off
Java
interface Command {
void execute();
void undo();
}
class Light {
boolean isOn = false;
void turnOn() { isOn = true; System.out.println("Light is on"); }
void turnOff() { isOn = false; System.out.println("Light is off"); }
}
class TurnOnCommand implements Command {
private final Light light;
TurnOnCommand(Light light) { this.light = light; }
public void execute() { light.turnOn(); }
public void undo() { light.turnOff(); }
}
// Usage
Light light = new Light();
Command cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo(); // Light is off
Explanation
The Command Pattern separates action invocation from execution:
- Command Interface: Declares
execute()and optionallyundo() - Concrete Command (
TurnOnCommand): Binds a receiver (Light) to an action (turnOn) - Receiver (
Light): The object that performs the actual work - Invoker: Calls
execute()on commands (e.g., a button, scheduler, or remote control)
By encapsulating requests as objects, you gain the ability to queue, log, and reverse operations.
Variants
| Variant | Use Case | Trade-off |
|---|---|---|
| Simple Command | Direct action with no undo | Easy to implement, limited flexibility |
| Undoable Command | Operations that can be reversed | Requires maintaining state for reversal |
| Macro Command | Composite of multiple commands | capable, but harder to undo atomically |
What Works
- Implement
undo()for every command if your system supports undo - Keep commands stateless when possible: Store receiver state, not command state
- Use a command history (stack) to support multi-level undo/redo
- Document side effects: Commands that affect external systems are harder to undo
- Consider immutability: Once configured, a command should not change its target
Common Mistakes
- Forgetting undo state: Commands that cannot be reversed break the undo stack
- Tight coupling: Commands that depend on global state instead of a specific receiver
- Over-engineering: Using Command for trivial, one-off operations that never need queuing or undo
- Synchronous assumptions: Not considering that commands may be executed asynchronously
- Missing idempotency: Running the same command twice produces different results
- Not handling command failures: Commands that throw exceptions can leave the system in an inconsistent state
- Storing too much state in commands: Commands should be lightweight; storing large objects affects memory and serialization
- Ignoring thread safety: Commands executed concurrently may access shared resources without proper synchronization
- Not logging command execution: Missing audit trails make debugging and compliance difficult
- Mixing concerns: Commands that perform multiple unrelated responsibilities violate single responsibility principle
Best Practices
-
Keep commands lightweight. Commands should be small, focused objects that encapsulate a single action. Avoid storing large amounts of data in commands.
-
Implement undo for all state-changing commands. If your system supports undo, every command that changes state should implement undo logic.
-
Use command factories for complex creation. When commands require complex setup, use factory methods or builders to encapsulate creation logic.
-
Handle exceptions gracefully. Commands should handle their own exceptions or provide clear error information to the invoker.
-
Make commands serializable. If you need to persist commands or send them over the network, ensure they can be serialized and deserialized.
-
Document command side effects. Clearly document any external side effects a command may have, especially those that are difficult to undo.
-
Use command history for debugging. Maintain a history of executed commands to help with debugging and audit trails.
-
Consider command composition. Use composite commands to build complex operations from simpler, reusable commands.
-
Validate command parameters. Validate command parameters before execution to fail fast and provide clear error messages.
-
Test commands in isolation. Write unit tests for each command independently, then integration tests for command chains and macros.
Frequently Asked Questions
What is the difference between Command and Strategy?
Strategy encapsulates interchangeable algorithms. Command encapsulates a request to perform an action, often with support for undo, queuing, and logging.
Can Command be used without undo?
Yes. The undo capability is optional. Many systems use Command solely for queuing and decoupling invokers from receivers.
How do I implement multi-level undo?
Maintain a stack of executed commands. Undo pops the stack and calls undo(). See Command with Undo/Redo for a full implementation. Redo pushes the command back and calls execute().
Can commands be executed asynchronously?
Yes. Implement async command interfaces with execute() and undo() methods that return promises or use async/await patterns. This is useful for I/O-bound operations like database calls or network requests.
How do I handle command failures?
Implement error handling at the command level or use command decorators that wrap execution with try-catch blocks. Consider implementing retry logic, circuit breaking, or fallback mechanisms for transient failures.
Should commands be serializable?
Yes, if you need to persist commands for replay, audit trails, or distributed execution. Ensure commands can be serialized to JSON, binary, or another format and deserialized without losing state.
How do I implement command queuing?
Use a queue data structure to hold commands and a worker thread or process to execute them. This enables deferred execution, background processing, and load balancing across workers.
Can commands be composed?
Yes. Use composite commands (macro commands) to combine multiple commands into a single executable unit. This is useful for complex operations that require multiple steps to be executed atomically.
How do I implement command logging?
Wrap commands with logging decorators that record execution details, parameters, and results. This provides audit trails and debugging information for command execution.
Should commands validate their parameters?
Yes. Validate command parameters before execution to fail fast and provide clear error messages. This prevents invalid commands from being executed and causing inconsistent state.
Related Resources
Observer Pattern
Define a subscription mechanism to notify multiple objects about events. A behavioral design pattern for event-driven communication.
PatternStrategy Pattern
Define a family of algorithms, encapsulate each one, and make them interchangeable. A behavioral design pattern for flexible behavior selection.
RecipeUnit Testing
How to write fast, deterministic unit tests with mocks and assertions in Python, JavaScript, and Java.
PatternInterpreter Pattern
Define a representation for a language's grammar along with an interpreter that uses the representation to interpret sentences. A behavioral design pattern for mini-languages.
PatternMemento Pattern
Capture and restore an object's internal state without violating encapsulation. A behavioral design pattern for undo/redo.
PatternTemplate Method Pattern
Define the skeleton of an algorithm in a base class, letting subclasses override specific steps without changing the algorithm's structure. A behavioral design pattern.