Low-level design3 min
Command Pattern
Type: Behavioral Pattern
Relevance: High. Used for undo/redo functionality, UI buttons, and job queues.
The Command pattern turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method argument, delay or queue a request's execution, and support undoable operations.
Real-Life Analogy
Think of a Restaurant Order. You (the client) give an order to the Waiter (the Invoker). The Waiter doesn't cook the food; they write the order on a piece of paper (the Command Object). They place this ticket on the kitchen counter. The Chef (the Receiver) later takes the ticket and cooks the food. Because the order is a physical piece of paper (an object), it can be queued, delayed, or even modified/undone before it's cooked.
The Problem
If you have a UI with dozens of buttons (Save, Copy, Paste, Undo), and you hardcode the business logic directly into the button click events, your UI becomes tightly coupled to your business logic. Furthermore, implementing an "Undo" button becomes nearly impossible because you haven't tracked what actions were performed.
How to Implement Command
- Extract the request details into a specific
Commandclass implementing anexecute()method. - The UI element (Invoker) only holds a reference to a
Commandobject and calls itsexecute()method.
Example in Code
// 1. The Command Interface
interface Command {
execute(): void;
undo(): void; // Crucial for undo/redo!
}
// 2. The Receiver (The actual business logic)
class Light {
public turnOn(): void { console.log("Light is ON"); }
public turnOff(): void { console.log("Light is OFF"); }
}
// 3. Concrete Commands
class TurnOnCommand implements Command {
constructor(private light: Light) {}
execute(): void { this.light.turnOn(); }
undo(): void { this.light.turnOff(); }
}
class TurnOffCommand implements Command {
constructor(private light: Light) {}
execute(): void { this.light.turnOff(); }
undo(): void { this.light.turnOn(); }
}
// 4. The Invoker (e.g., A Smart Home Remote Button)
class RemoteControl {
private history: Command[] = [];
public pressButton(cmd: Command): void {
cmd.execute();
this.history.push(cmd); // Save to history for undo!
}
public pressUndo(): void {
const lastCmd = this.history.pop();
if (lastCmd) {
console.log("Undoing last action...");
lastCmd.undo();
}
}
}
// Client Code
const livingRoomLight = new Light();
const turnOn = new TurnOnCommand(livingRoomLight);
const turnOff = new TurnOffCommand(livingRoomLight);
const remote = new RemoteControl();
// The remote doesn't know HOW the light works. It just executes commands.
remote.pressButton(turnOn); // Light is ON
remote.pressButton(turnOff); // Light is OFF
// We can undo!
remote.pressUndo(); // Light is ON
Class Diagram
Why is it Important?
- Decoupling: Decouples classes that invoke operations from classes that perform these operations.
- Undo/Redo: It is the standard, most effective way to implement undo/redo functionality (by keeping a stack of executed Command objects and calling
undo()on them). - Queuing: Command objects can be serialized, saved to a database, and executed later by a background worker (e.g., Job Queues).