Command Pattern
What is the Command Pattern?
The Command Pattern is a behavioral design pattern that transforms a request into a stand-alone object. This object contains all the information needed to execute the action, including which method to call, the arguments to pass, and the object that should perform the operation.
Instead of directly invoking an action on an object, you encapsulate that request as a command object. This separation provides several powerful capabilities: you can delay execution, queue commands for later, store command history for undo/redo functionality, or parameterize objects with different commands.
The pattern follows the principle of encapsulation: the object making the request doesn’t need to know how the operation gets executed, only that it will be handled correctly.
Key Components
Understanding the Command Pattern requires knowing its five essential parts:
Command Interface defines the contract that all concrete commands must follow. At minimum, it specifies an execute() method that triggers the action.
ConcreteCommand implements the Command interface and defines the binding between a Receiver and an action. It holds a reference to the Receiver and calls its methods to fulfill the request.
Receiver is the object that knows how to perform the actual work. It contains the business logic that gets executed when the command’s execute() method is called.
Invoker is responsible for storing and managing command objects. It triggers the command by calling its execute() method. The invoker doesn’t know anything about the specific command implementation—it only knows about the Command interface.
Client creates the ConcreteCommand objects and sets up their associations with Receivers. It configures which commands get executed by which invokers.
JavaScript Implementation
JavaScript’s flexibility allows several approaches to implementing the Command Pattern. Here’s a practical implementation:
// Receiver: knows how to perform the action
class Light {
constructor() {
this.isOn = false;
}
turnOn() {
this.isOn = true;
console.log("Light is ON");
}
turnOff() {
this.isOn = false;
console.log("Light is OFF");
}
}
// Command interface (using a convention)
class Command {
execute() {}
undo() {}
}
// ConcreteCommand: binds action to receiver
class LightOnCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOn();
}
undo() {
this.light.turnOff();
}
}
class LightOffCommand {
constructor(light) {
this.light = light;
}
execute() {
this.light.turnOff();
}
undo() {
this.light.turnOn();
}
}
// Invoker: manages command execution
class RemoteControl {
constructor() {
this.history = [];
}
executeCommand(command) {
command.execute();
this.history.push(command);
}
undoLastCommand() {
const command = this.history.pop();
if (command) {
command.undo();
}
}
}
// Client code
const light = new Light();
const lightOn = new LightOnCommand(light);
const lightOff = new LightOffCommand(light);
const remote = new RemoteControl();
remote.executeCommand(lightOn); // Light is ON
remote.executeCommand(lightOff); // Light is OFF
remote.undoLastCommand(); // Light is ON
This example demonstrates the core pattern: commands encapsulate actions, and the invoker manages their execution with support for undo operations.
Modern JavaScript Approach
With ES6+ features, you can implement the pattern more concisely using classes and factory functions:
// Factory function approach for creating commands
const createCommand = (receiver, action, reverseAction) => ({
execute: () => receiver[action](),
undo: () => receiver[reverseAction]()
});
// Usage
const lightCommands = {
on: createCommand(light, 'turnOn', 'turnOff'),
off: createCommand(light, 'turnOff', 'turnOn')
};
remote.executeCommand(lightCommands.on);
This approach reduces boilerplate while maintaining the pattern’s flexibility.
Practical Use Cases
Undo and Redo Functionality
The Command Pattern excels at implementing undo/redo systems. Each command stores enough information to reverse itself, and a history stack tracks executed commands. When users request an undo, the invoker pops the command and calls its undo() method.
Command Queuing
Commands can be queued for later execution. This is useful for batch operations, scheduling, or implementing task automation. The invoker maintains a queue and processes commands sequentially or at scheduled times.
Macros
A macro command executes multiple commands as a single unit. You create a CompositeCommand that holds an array of commands and iterates through them in its execute() method. This allows users to perform complex multi-step operations with a single action.
Transaction Management
Database operations benefit from command encapsulation. Each operation becomes a command that can be executed, committed, or rolled back. If any operation fails, you can undo all previous operations in the transaction.
Benefits and Drawbacks
Benefits
The Command Pattern provides loose coupling between objects that invoke actions and those that perform them. The invoker doesn’t need to know the details of how operations execute—it simply triggers commands through a consistent interface.
The pattern supports extensibility: adding new commands doesn’t require modifying existing code. You create new ConcreteCommand classes that implement the Command interface, and the invoker works with them automatically.
Testing becomes easier because you can mock commands and verify that the invoker calls them correctly. Each command can be tested in isolation.
Drawbacks
The pattern introduces additional complexity with multiple classes for what might be simple function calls. For straightforward operations, this abstraction layer adds unnecessary overhead.
Each command must store sufficient information to execute and undo itself. This can increase memory usage, especially for commands that need to store large amounts of state.
The pattern requires careful design to ensure commands remain focused and cohesive. Commands that try to do too much become difficult to maintain and test.
When to Use
Consider the Command Pattern when you need to parameterize objects with actions, queue operations for later execution, implement undo/redo functionality, or support macro operations. The pattern shines in applications where operations need to be logged, reversed, or executed conditionally based on runtime factors.
For simple scenarios with direct object interactions, the additional abstraction may not be worth the complexity. Evaluate your specific requirements before deciding whether the Command Pattern fits your needs.
See Also
- Observer Pattern — another behavioral pattern for object communication
- Singleton Pattern — creational pattern for single instance management
- Decorator Pattern — structural pattern for adding behavior dynamically