Command Pattern: Encapsulating Actions as Objects
Situation: You’re Building a UI with Buttons
You have a GUI or CLI where a user can:
- Click “Save”
- Click “Print”
- Click “Undo”
- Click “Redo”
You write something like:
if (button.equals("save")) saveDog();
else if (button.equals("print")) printDoc();
else if (button.equals("undo")) undo();
Then Requirements Start Stacking
- Add keyboard shortcuts for each action
- Add macros (run a sequence of commands)
- Add undo history
- Add dynamic remapping of buttons
- Each action might need different parameters
Now your code is tangled. Logic is everywhere. You can’t:
- Queue actions
- Log what was done
- Revert past actions
- Run same logic later with same behavior
You’re writing “action triggers” and “execution logic” in the same place - it’s a giant ball of mud.
What’s the Problem?
- You’re mixing what to do with how to trigger it
- No clean way to queue, undo, or reuse actions
- Impossible to treat actions like objects
- No audit trail, no flexibility
How Command Pattern Saves You
Command pattern says:
“Wrap every action inside a Command object - now it can be passed around, stored, and executed when needed.”
Instead of directly calling methods, you create command classes that encapsulate an action and its parameters. Each command implements a common interface with methods like execute()
and undo()
.
Interface
// Command interface
interface Command {
execute(): void;
undo(): void;
}
Concrete commands
// Concrete commands
class SaveCommand implements Command {
private document: Document;
constructor(document: Document) {
this.document = document;
}
execute() {
this.document.save();
}
undo() {
// Restore previous state
}
}
Invoker class (e.g., a button)
// Command invoker
class Button {
private command: Command;
setCommand(command: Command) {
this.command = command;
}
click() {
this.command.execute();
}
}
Usage
Button saveBtn = new Button(new SaveCommand());
Button printBtn = new Button(new PrintCommand());
saveBtn.click(); // Output: Saving the document...
printBtn.click(); // Output: Printing the document...
Benefits of the Command Pattern
- Separation of concerns: The invoker is decoupled from the receiver
- Extensibility: Add new commands without changing existing code
- History: Store command objects for audit logs or undo functionality
- Queuing: Commands can be scheduled and executed later
- Composability: Create macro commands that execute multiple commands
- Transactional behavior: Roll back a sequence of commands if one fails
Real-World Use Cases
- GUI actions (buttons, menu items, keyboard shortcuts)
- Transactional systems where operations need to be rolled back
- Remote procedure calls where commands are serialized and sent over a network
- Task schedulers and job queues
- Multi-level undo/redo functionality
- Gaming input systems
When to Use It
Use the Command Pattern when you need to:
- Parameterize objects with operations
- Queue, schedule, or execute operations remotely
- Support undoable operations
- Structure a system around high-level operations
- Implement callback functionality in a type-safe way
By encapsulating actions as objects, the Command Pattern brings flexibility and structure to your code, making it easier to extend and maintain as requirements grow.