Design Patterns
Command Pattern
Master the Command Pattern in JavaScript. Learn to encapsulate requests as objects, implement undo/redo buffers, and decouple request issuers from executors.
1. Introduction
The Command Pattern is a behavioral design pattern that encapsulates a request as a standalone object containing all information about the request. This allows you to parameterize methods, delay request execution, and implement undo/redo operations.
2. Why It Matters
Calling methods on receiver objects directly couples the sender to the receiver. If you want to build features like an action history log or support undo/redo buttons in an editor, direct method calls won't work. The Command pattern solves this by wrapping actions in standardized command objects.
3. Real-World Analogy
Think of Ordering Food at a Restaurant:
- Direct Execution (Coupled kitchen): You walk into the kitchen, stand next to the stove, and yell instructions at the chef: "Cook a steak!" You must manage the chef and stove directly.
- Command Pattern (Order ticket): You sit at the table. The waiter writes your order on an order ticket (the Command object): "Table 5: Steak - Medium". The ticket is placed on the kitchen counter. The chef (receiver) processes the ticket whenever they are ready. Symmetrically, if you change your mind before cooking starts, the waiter can take the ticket back (undo the command).
4. Implementing the Command Pattern
A command object typically implements an execute() method and optionally an undo() method:
5. Command Invoker & History Manager
The Invoker class executes commands and maintains an action history log to support undo operations:
6. Practical Example
This script demonstrates using the Command pattern to schedule and execute background operations in a queue:
7. Common Mistakes
- Overusing the Command pattern for simple actions: If your application only needs to trigger direct method calls and doesn't require undo history or job queuing, wrapping every action in a Command class adds unnecessary complexity. Use standard method calls instead.
8. Quick Quiz
Q1: What is the primary role of the Invoker class in the Command Pattern?
A) To implement the core business logic of the action
B) To execute commands and maintain a history log for undo operations
Answer: B — The Invoker triggers the commands and maintains the history stack to support undo/redo operations.
9. Scenario-Based Challenge
The Text Editor Undo/Redo Engine:
You write a text editor backend: TextDocument. Users can insert text. Implement an insert command InsertTextCommand that accepts a character position and a text string, supporting undo operations by deleting the inserted characters. Write this command class.
10. Debugging Exercise
Explain why this command fails to undo correctly, and how to fix it:
class Light { constructor() { this.brightness = 0; } set(val) { this.brightness = val; } }class SetBrightnessCommand { constructor(light, value) { this.light = light; this.value = value; }
execute() { this.light.set(this.value); }
// Bug: setting the brightness to 0 during undo assumes the previous state was 0! undo() { this.light.set(0); } }
View Solution
Diagnosis: The undo() method hardcodes the fallback brightness to 0. If the light was at 50% brightness before the command executed, undoing the command will set it to 0% instead of restoring the previous 50% state.
Fix: Save the previous state inside the command object during the execute() call, and restore it during the undo() call:
class SetBrightnessCommand { #prevValue = 0; // Store stateconstructor(light, value) { this.light = light; this.value = value; }
execute() { this.#prevValue = this.light.brightness; // Save current state before modifying! this.light.set(this.value); }
undo() { this.light.set(this.#prevValue); // Restore previous state correctly! } }
11. Interview Questions
🟢 Q1: Explain how the Command Pattern decouples the sender from the receiver.
Answer:
• Coupled Design: The sender must know the receiver's class definition and method signatures to execute actions directly.
• Command Decoupling: The sender only interacts with a standardized command interface (calling its execute() method). The command object encapsulates the target receiver instance and its method arguments, acting as a buffer. The sender doesn't need to know how the receiver executes the action, decoupling them.
12. Production Considerations
- • Memory Management: Maintaining a history log of commands can consume memory over long sessions. Limit the size of the undo stack (e.g. keeping only the last 50 commands) to prevent memory leaks in production.