Command Pattern
Command Pattern: Turning Requests into Objects
Section titled “Command Pattern: Turning Requests into Objects”Now let’s dive into the Command Pattern - one of the most versatile behavioral design patterns that encapsulates a request as an object, letting you parameterize clients with different requests, queue operations, log them, and support undoable operations.
Why Command Pattern?
Section titled “Why Command Pattern?”Imagine you’re using a text editor. You type some text, then click Undo - the text disappears. Click Redo - it comes back. How does this work? The Command Pattern! Each action (typing, deleting, formatting) is wrapped in a command object that knows how to execute itself AND how to undo itself.
The Command Pattern turns requests into stand-alone objects containing all information about the request. This transformation lets you pass requests as method arguments, delay or queue a request’s execution, and support undoable operations.
What’s the Use of Command Pattern?
Section titled “What’s the Use of Command Pattern?”The Command Pattern is useful when:
- You need undo/redo functionality - Commands can be reversed
- You want to queue operations - Execute commands later or in sequence
- You need to log operations - Commands can be serialized and logged
- You want to decouple sender from receiver - Invoker doesn’t know about receiver
- You need transactional behavior - Rollback if something fails
What Happens If We Don’t Use Command Pattern?
Section titled “What Happens If We Don’t Use Command Pattern?”Without the Command Pattern, you might:
- Tight coupling - Invoker directly calls receiver methods
- No undo support - Have to manually track all changes
- Hard to queue operations - Operations execute immediately
- No operation logging - Can’t track what was done
- Scattered code - Same operation code repeated everywhere
Simple Example: The Remote Control
Section titled “Simple Example: The Remote Control”Let’s start with a super simple example that anyone can understand!
Visual Representation
Section titled “Visual Representation”Interaction Flow
Section titled “Interaction Flow”Here’s how the Command Pattern works in practice - showing how commands are executed and undone:
sequenceDiagram
participant Client
participant Invoker as RemoteControl
participant Command as LightOnCommand
participant Receiver as Light
Client->>Command: new LightOnCommand(light)
activate Command
Command->>Command: Store light reference
Command-->>Client: Command created
deactivate Command
Client->>Invoker: set_command(lightOnCommand)
Invoker->>Invoker: Store command
Client->>Invoker: press_button()
activate Invoker
Invoker->>Command: execute()
activate Command
Command->>Receiver: turn_on()
activate Receiver
Receiver->>Receiver: Light is ON
Receiver-->>Command: Done
deactivate Receiver
Command-->>Invoker: Executed
deactivate Command
Invoker->>Invoker: Store for undo
Invoker-->>Client: Done
deactivate Invoker
Note over Client,Receiver: User changes mind...
Client->>Invoker: press_undo()
activate Invoker
Invoker->>Command: undo()
activate Command
Command->>Receiver: turn_off()
activate Receiver
Receiver->>Receiver: Light is OFF
Receiver-->>Command: Done
deactivate Receiver
Command-->>Invoker: Undone
deactivate Command
Invoker-->>Client: Done
deactivate Invoker
The Problem
Section titled “The Problem”You’re building a smart home system with a remote control. The remote needs to control different devices (lights, fans, TVs). Without Command Pattern:
1# ❌ Without Command Pattern - Tight coupling, no undo!2
3class Light:4 def __init__(self, location: str):5 self.location = location6 self.is_on = False7
8 def turn_on(self):9 self.is_on = True10 print(f"💡 {self.location} light is ON")11
12 def turn_off(self):13 self.is_on = False14 print(f"💡 {self.location} light is OFF")15
16class Fan:17 def __init__(self, location: str):18 self.location = location19 self.speed = 020
21 def set_speed(self, speed: int):22 self.speed = speed23 print(f"🌀 {self.location} fan speed: {speed}")24
25class RemoteControl:26 def __init__(self):27 # Problem: Remote knows about ALL device types!28 self.light = None29 self.fan = None30
31 def press_light_on(self):32 if self.light:33 self.light.turn_on()34
35 def press_light_off(self):36 if self.light:37 self.light.turn_off()38
39 def press_fan_high(self):40 if self.fan:41 self.fan.set_speed(3)42
43 def press_fan_off(self):44 if self.fan:45 self.fan.set_speed(0)46
47 # Problems:48 # - Remote tightly coupled to device types49 # - Need to modify Remote for each new device50 # - No undo functionality51 # - No way to queue commands52
53# Usage54remote = RemoteControl()55remote.light = Light("Living Room")56remote.press_light_on()57# How do we undo? No easy way!1// ❌ Without Command Pattern - Tight coupling, no undo!2
3class Light {4 private String location;5 private boolean isOn = false;6
7 public Light(String location) {8 this.location = location;9 }10
11 public void turnOn() {12 isOn = true;13 System.out.println("💡 " + location + " light is ON");14 }15
16 public void turnOff() {17 isOn = false;18 System.out.println("💡 " + location + " light is OFF");19 }20}21
22class Fan {23 private String location;24 private int speed = 0;25
26 public Fan(String location) {27 this.location = location;28 }29
30 public void setSpeed(int speed) {31 this.speed = speed;32 System.out.println("🌀 " + location + " fan speed: " + speed);33 }34}35
36class RemoteControl {37 // Problem: Remote knows about ALL device types!38 private Light light = null;39 private Fan fan = null;40
41 public void setLight(Light light) {42 this.light = light;43 }44
45 public void setFan(Fan fan) {46 this.fan = fan;47 }48
49 public void pressLightOn() {50 if (light != null) {51 light.turnOn();52 }53 }54
55 public void pressLightOff() {56 if (light != null) {57 light.turnOff();58 }59 }60
61 public void pressFanHigh() {62 if (fan != null) {63 fan.setSpeed(3);64 }65 }66
67 public void pressFanOff() {68 if (fan != null) {69 fan.setSpeed(0);70 }71 }72
73 // Problems:74 // - Remote tightly coupled to device types75 // - Need to modify Remote for each new device76 // - No undo functionality77 // - No way to queue commands78}79
80// Usage81public class Main {82 public static void main(String[] args) {83 RemoteControl remote = new RemoteControl();84 remote.setLight(new Light("Living Room"));85 remote.pressLightOn();86 // How do we undo? No easy way!87 }88}Problems:
- Remote control is tightly coupled to all device types
- Adding new devices requires modifying RemoteControl
- No undo functionality - can’t reverse actions
- No way to queue or log commands
The Solution: Command Pattern
Section titled “The Solution: Command Pattern”Class Structure
Section titled “Class Structure”classDiagram
class Command {
<<interface>>
+execute() void
+undo() void
}
class LightOnCommand {
-light: Light
+execute() void
+undo() void
}
class LightOffCommand {
-light: Light
+execute() void
+undo() void
}
class FanHighCommand {
-fan: Fan
-prevSpeed: int
+execute() void
+undo() void
}
class RemoteControl {
-command: Command
-history: List~Command~
+set_command(cmd) void
+press_button() void
+press_undo() void
}
class Light {
+turn_on() void
+turn_off() void
}
class Fan {
+set_speed(speed) void
}
Command <|.. LightOnCommand : implements
Command <|.. LightOffCommand : implements
Command <|.. FanHighCommand : implements
RemoteControl --> Command : invokes
LightOnCommand --> Light : controls
LightOffCommand --> Light : controls
FanHighCommand --> Fan : controls
note for Command "Encapsulates action\nand its undo"
note for RemoteControl "Invoker - doesn't know\nabout receivers"
1from abc import ABC, abstractmethod2from typing import List3
4# Step 1: Define the Command interface5class Command(ABC):6 """Command interface - all commands implement this"""7
8 @abstractmethod9 def execute(self) -> None:10 """Execute the command"""11 pass12
13 @abstractmethod14 def undo(self) -> None:15 """Undo the command"""16 pass17
18# Step 2: Create Receiver classes (devices)19class Light:20 """Receiver - the device that performs the action"""21
22 def __init__(self, location: str):23 self.location = location24 self.is_on = False25
26 def turn_on(self) -> None:27 self.is_on = True28 print(f"💡 {self.location} light is ON")29
30 def turn_off(self) -> None:31 self.is_on = False32 print(f"💡 {self.location} light is OFF")33
34class Fan:35 """Receiver - fan device"""36
37 def __init__(self, location: str):38 self.location = location39 self.speed = 040
41 def set_speed(self, speed: int) -> None:42 self.speed = speed43 if speed == 0:44 print(f"🌀 {self.location} fan is OFF")45 else:46 print(f"🌀 {self.location} fan speed: {speed}")47
48 def get_speed(self) -> int:49 return self.speed50
51# Step 3: Create Concrete Commands52class LightOnCommand(Command):53 """Concrete command - turn light on"""54
55 def __init__(self, light: Light):56 self.light = light57
58 def execute(self) -> None:59 self.light.turn_on()60
61 def undo(self) -> None:62 self.light.turn_off()63
64class LightOffCommand(Command):65 """Concrete command - turn light off"""66
67 def __init__(self, light: Light):68 self.light = light69
70 def execute(self) -> None:71 self.light.turn_off()72
73 def undo(self) -> None:74 self.light.turn_on()75
76class FanHighCommand(Command):77 """Concrete command - set fan to high speed"""78
79 def __init__(self, fan: Fan):80 self.fan = fan81 self.prev_speed = 0 # Store previous speed for undo82
83 def execute(self) -> None:84 self.prev_speed = self.fan.get_speed() # Save for undo85 self.fan.set_speed(3)86
87 def undo(self) -> None:88 self.fan.set_speed(self.prev_speed)89
90class FanOffCommand(Command):91 """Concrete command - turn fan off"""92
93 def __init__(self, fan: Fan):94 self.fan = fan95 self.prev_speed = 096
97 def execute(self) -> None:98 self.prev_speed = self.fan.get_speed()99 self.fan.set_speed(0)100
101 def undo(self) -> None:102 self.fan.set_speed(self.prev_speed)103
104# Step 4: Create NoCommand (Null Object Pattern)105class NoCommand(Command):106 """Null command - does nothing"""107
108 def execute(self) -> None:109 pass110
111 def undo(self) -> None:112 pass113
114# Step 5: Create the Invoker115class RemoteControl:116 """Invoker - triggers commands"""117
118 def __init__(self, num_slots: int = 4):119 self.num_slots = num_slots120 self.on_commands: List[Command] = [NoCommand()] * num_slots121 self.off_commands: List[Command] = [NoCommand()] * num_slots122 self.history: List[Command] = [] # For undo123
124 def set_command(self, slot: int, on_cmd: Command, off_cmd: Command) -> None:125 """Set commands for a slot"""126 self.on_commands[slot] = on_cmd127 self.off_commands[slot] = off_cmd128 print(f"✅ Slot {slot} configured")129
130 def press_on(self, slot: int) -> None:131 """Press the ON button for a slot"""132 print(f"\n🔘 Pressing ON button for slot {slot}")133 command = self.on_commands[slot]134 command.execute()135 self.history.append(command)136
137 def press_off(self, slot: int) -> None:138 """Press the OFF button for a slot"""139 print(f"\n🔘 Pressing OFF button for slot {slot}")140 command = self.off_commands[slot]141 command.execute()142 self.history.append(command)143
144 def press_undo(self) -> None:145 """Undo the last command"""146 if self.history:147 print("\n⏪ Pressing UNDO button")148 command = self.history.pop()149 command.undo()150 else:151 print("\n⏪ Nothing to undo")152
153# Step 6: Use the pattern154def main():155 # Create receivers (devices)156 living_room_light = Light("Living Room")157 bedroom_fan = Fan("Bedroom")158
159 # Create commands160 light_on = LightOnCommand(living_room_light)161 light_off = LightOffCommand(living_room_light)162 fan_high = FanHighCommand(bedroom_fan)163 fan_off = FanOffCommand(bedroom_fan)164
165 # Create invoker (remote)166 remote = RemoteControl()167
168 # Configure remote169 remote.set_command(0, light_on, light_off)170 remote.set_command(1, fan_high, fan_off)171
172 # Use the remote173 remote.press_on(0) # Light ON174 remote.press_on(1) # Fan HIGH175 remote.press_off(0) # Light OFF176
177 # Undo operations178 remote.press_undo() # Light back ON179 remote.press_undo() # Fan back to previous speed180 remote.press_undo() # Light back OFF181
182 print("\n✅ Command Pattern: Commands encapsulated with undo support!")183
184if __name__ == "__main__":185 main()1import java.util.*;2
3// Step 1: Define the Command interface4interface Command {5 /**6 * Command interface - all commands implement this7 */8 void execute();9 void undo();10}11
12// Step 2: Create Receiver classes (devices)13class Light {14 /**15 * Receiver - the device that performs the action16 */17 private String location;18 private boolean isOn = false;19
20 public Light(String location) {21 this.location = location;22 }23
24 public void turnOn() {25 isOn = true;26 System.out.println("💡 " + location + " light is ON");27 }28
29 public void turnOff() {30 isOn = false;31 System.out.println("💡 " + location + " light is OFF");32 }33}34
35class Fan {36 /**37 * Receiver - fan device38 */39 private String location;40 private int speed = 0;41
42 public Fan(String location) {43 this.location = location;44 }45
46 public void setSpeed(int speed) {47 this.speed = speed;48 if (speed == 0) {49 System.out.println("🌀 " + location + " fan is OFF");50 } else {51 System.out.println("🌀 " + location + " fan speed: " + speed);52 }53 }54
55 public int getSpeed() {56 return speed;57 }58}59
60// Step 3: Create Concrete Commands61class LightOnCommand implements Command {62 /**63 * Concrete command - turn light on64 */65 private Light light;66
67 public LightOnCommand(Light light) {68 this.light = light;69 }70
71 @Override72 public void execute() {73 light.turnOn();74 }75
76 @Override77 public void undo() {78 light.turnOff();79 }80}81
82class LightOffCommand implements Command {83 /**84 * Concrete command - turn light off85 */86 private Light light;87
88 public LightOffCommand(Light light) {89 this.light = light;90 }91
92 @Override93 public void execute() {94 light.turnOff();95 }96
97 @Override98 public void undo() {99 light.turnOn();100 }101}102
103class FanHighCommand implements Command {104 /**105 * Concrete command - set fan to high speed106 */107 private Fan fan;108 private int prevSpeed = 0; // Store previous speed for undo109
110 public FanHighCommand(Fan fan) {111 this.fan = fan;112 }113
114 @Override115 public void execute() {116 prevSpeed = fan.getSpeed(); // Save for undo117 fan.setSpeed(3);118 }119
120 @Override121 public void undo() {122 fan.setSpeed(prevSpeed);123 }124}125
126class FanOffCommand implements Command {127 /**128 * Concrete command - turn fan off129 */130 private Fan fan;131 private int prevSpeed = 0;132
133 public FanOffCommand(Fan fan) {134 this.fan = fan;135 }136
137 @Override138 public void execute() {139 prevSpeed = fan.getSpeed();140 fan.setSpeed(0);141 }142
143 @Override144 public void undo() {145 fan.setSpeed(prevSpeed);146 }147}148
149// Step 4: Create NoCommand (Null Object Pattern)150class NoCommand implements Command {151 /**152 * Null command - does nothing153 */154 @Override155 public void execute() {}156
157 @Override158 public void undo() {}159}160
161// Step 5: Create the Invoker162class RemoteControl {163 /**164 * Invoker - triggers commands165 */166 private int numSlots;167 private Command[] onCommands;168 private Command[] offCommands;169 private List<Command> history; // For undo170
171 public RemoteControl(int numSlots) {172 this.numSlots = numSlots;173 onCommands = new Command[numSlots];174 offCommands = new Command[numSlots];175 history = new ArrayList<>();176
177 // Initialize with NoCommand178 NoCommand noCommand = new NoCommand();179 for (int i = 0; i < numSlots; i++) {180 onCommands[i] = noCommand;181 offCommands[i] = noCommand;182 }183 }184
185 public void setCommand(int slot, Command onCmd, Command offCmd) {186 // Set commands for a slot187 onCommands[slot] = onCmd;188 offCommands[slot] = offCmd;189 System.out.println("✅ Slot " + slot + " configured");190 }191
192 public void pressOn(int slot) {193 // Press the ON button for a slot194 System.out.println("\n🔘 Pressing ON button for slot " + slot);195 Command command = onCommands[slot];196 command.execute();197 history.add(command);198 }199
200 public void pressOff(int slot) {201 // Press the OFF button for a slot202 System.out.println("\n🔘 Pressing OFF button for slot " + slot);203 Command command = offCommands[slot];204 command.execute();205 history.add(command);206 }207
208 public void pressUndo() {209 // Undo the last command210 if (!history.isEmpty()) {211 System.out.println("\n⏪ Pressing UNDO button");212 Command command = history.remove(history.size() - 1);213 command.undo();214 } else {215 System.out.println("\n⏪ Nothing to undo");216 }217 }218}219
220// Step 6: Use the pattern221public class Main {222 public static void main(String[] args) {223 // Create receivers (devices)224 Light livingRoomLight = new Light("Living Room");225 Fan bedroomFan = new Fan("Bedroom");226
227 // Create commands228 LightOnCommand lightOn = new LightOnCommand(livingRoomLight);229 LightOffCommand lightOff = new LightOffCommand(livingRoomLight);230 FanHighCommand fanHigh = new FanHighCommand(bedroomFan);231 FanOffCommand fanOff = new FanOffCommand(bedroomFan);232
233 // Create invoker (remote)234 RemoteControl remote = new RemoteControl(4);235
236 // Configure remote237 remote.setCommand(0, lightOn, lightOff);238 remote.setCommand(1, fanHigh, fanOff);239
240 // Use the remote241 remote.pressOn(0); // Light ON242 remote.pressOn(1); // Fan HIGH243 remote.pressOff(0); // Light OFF244
245 // Undo operations246 remote.pressUndo(); // Light back ON247 remote.pressUndo(); // Fan back to previous speed248 remote.pressUndo(); // Light back OFF249
250 System.out.println("\n✅ Command Pattern: Commands encapsulated with undo support!");251 }252}Real-World Software Example: Text Editor with Undo/Redo
Section titled “Real-World Software Example: Text Editor with Undo/Redo”Now let’s see a realistic software example - a text editor that supports full undo/redo functionality.
The Problem
Section titled “The Problem”You’re building a text editor that needs to support multiple operations (typing, deleting, formatting) with full undo/redo capability. Without Command Pattern:
1# ❌ Without Command Pattern - Undo is a nightmare!2
3class TextEditor:4 def __init__(self):5 self.content = ""6 # Problem: Need to track every change manually!7 self.history = []8
9 def type_text(self, text: str, position: int):10 # Insert text at position11 old_content = self.content12 self.content = self.content[:position] + text + self.content[position:]13 # Problem: How to undo? Store entire content?14 self.history.append(("type", position, text, old_content))15 print(f"Typed: '{text}'")16
17 def delete_text(self, start: int, end: int):18 # Delete text in range19 old_content = self.content20 deleted = self.content[start:end]21 self.content = self.content[:start] + self.content[end:]22 # Problem: Complex undo tracking23 self.history.append(("delete", start, end, deleted, old_content))24 print(f"Deleted: '{deleted}'")25
26 def undo(self):27 if not self.history:28 return29
30 # Problem: Complex logic for each operation type!31 last_op = self.history.pop()32
33 if last_op[0] == "type":34 # Undo type - restore old content35 self.content = last_op[3]36 elif last_op[0] == "delete":37 # Undo delete - restore old content38 self.content = last_op[4]39 # Problem: Need to handle EVERY operation type!40 # - What about bold? italic? find-replace?41 # - Code becomes unmaintainable!42
43# Usage44editor = TextEditor()45editor.type_text("Hello", 0)46editor.type_text(" World", 5)47editor.delete_text(5, 11) # Delete " World"48editor.undo() # Should restore " World" - but complex!1// ❌ Without Command Pattern - Undo is a nightmare!2
3import java.util.*;4
5class TextEditor {6 private StringBuilder content = new StringBuilder();7 // Problem: Need to track every change manually!8 private List<Object[]> history = new ArrayList<>();9
10 public void typeText(String text, int position) {11 // Insert text at position12 String oldContent = content.toString();13 content.insert(position, text);14 // Problem: How to undo? Store entire content?15 history.add(new Object[]{"type", position, text, oldContent});16 System.out.println("Typed: '" + text + "'");17 }18
19 public void deleteText(int start, int end) {20 // Delete text in range21 String oldContent = content.toString();22 String deleted = content.substring(start, end);23 content.delete(start, end);24 // Problem: Complex undo tracking25 history.add(new Object[]{"delete", start, end, deleted, oldContent});26 System.out.println("Deleted: '" + deleted + "'");27 }28
29 public void undo() {30 if (history.isEmpty()) {31 return;32 }33
34 // Problem: Complex logic for each operation type!35 Object[] lastOp = history.remove(history.size() - 1);36
37 if (lastOp[0].equals("type")) {38 // Undo type - restore old content39 content = new StringBuilder((String) lastOp[3]);40 } else if (lastOp[0].equals("delete")) {41 // Undo delete - restore old content42 content = new StringBuilder((String) lastOp[4]);43 }44 // Problem: Need to handle EVERY operation type!45 // - What about bold? italic? find-replace?46 // - Code becomes unmaintainable!47 }48}49
50// Usage51public class Main {52 public static void main(String[] args) {53 TextEditor editor = new TextEditor();54 editor.typeText("Hello", 0);55 editor.typeText(" World", 5);56 editor.deleteText(5, 11); // Delete " World"57 editor.undo(); // Should restore " World" - but complex!58 }59}Problems:
- Complex undo tracking for each operation type
- Need to modify editor for each new operation
- History management is complex and error-prone
- No redo support
- Hard to serialize/log operations
The Solution: Command Pattern
Section titled “The Solution: Command Pattern”Class Structure
Section titled “Class Structure”classDiagram
class EditorCommand {
<<interface>>
+execute() void
+undo() void
}
class TypeCommand {
-document: Document
-text: str
-position: int
+execute() void
+undo() void
}
class DeleteCommand {
-document: Document
-start: int
-end: int
-deletedText: str
+execute() void
+undo() void
}
class BoldCommand {
-document: Document
-start: int
-end: int
+execute() void
+undo() void
}
class Editor {
-document: Document
-undoStack: List~EditorCommand~
-redoStack: List~EditorCommand~
+execute_command(cmd) void
+undo() void
+redo() void
}
class Document {
-content: str
+insert(pos, text) void
+delete(start, end) str
+get_content() str
}
EditorCommand <|.. TypeCommand : implements
EditorCommand <|.. DeleteCommand : implements
EditorCommand <|.. BoldCommand : implements
Editor --> EditorCommand : manages
TypeCommand --> Document : modifies
DeleteCommand --> Document : modifies
BoldCommand --> Document : modifies
Editor --> Document : owns
note for Editor "Manages undo/redo stacks"
note for EditorCommand "Each command knows\nhow to undo itself"
1from abc import ABC, abstractmethod2from typing import List, Optional3from dataclasses import dataclass, field4
5# Step 1: Create the Document (Receiver)6class Document:7 """Receiver - the document being edited"""8
9 def __init__(self):10 self._content = ""11
12 def insert(self, position: int, text: str) -> None:13 """Insert text at position"""14 self._content = self._content[:position] + text + self._content[position:]15
16 def delete(self, start: int, end: int) -> str:17 """Delete text in range and return deleted text"""18 deleted = self._content[start:end]19 self._content = self._content[:start] + self._content[end:]20 return deleted21
22 def get_content(self) -> str:23 """Get document content"""24 return self._content25
26 def get_length(self) -> int:27 """Get document length"""28 return len(self._content)29
30# Step 2: Define the Command interface31class EditorCommand(ABC):32 """Command interface for editor operations"""33
34 @abstractmethod35 def execute(self) -> None:36 """Execute the command"""37 pass38
39 @abstractmethod40 def undo(self) -> None:41 """Undo the command"""42 pass43
44 @property45 @abstractmethod46 def description(self) -> str:47 """Description of the command for logging"""48 pass49
50# Step 3: Create Concrete Commands51class TypeCommand(EditorCommand):52 """Command to type/insert text"""53
54 def __init__(self, document: Document, text: str, position: int):55 self._document = document56 self._text = text57 self._position = position58
59 def execute(self) -> None:60 self._document.insert(self._position, self._text)61
62 def undo(self) -> None:63 # Delete the text that was typed64 self._document.delete(self._position, self._position + len(self._text))65
66 @property67 def description(self) -> str:68 return f"Type '{self._text}' at position {self._position}"69
70class DeleteCommand(EditorCommand):71 """Command to delete text"""72
73 def __init__(self, document: Document, start: int, end: int):74 self._document = document75 self._start = start76 self._end = end77 self._deleted_text: str = "" # Store for undo78
79 def execute(self) -> None:80 # Store deleted text for undo81 self._deleted_text = self._document.delete(self._start, self._end)82
83 def undo(self) -> None:84 # Re-insert the deleted text85 self._document.insert(self._start, self._deleted_text)86
87 @property88 def description(self) -> str:89 return f"Delete text from {self._start} to {self._end}"90
91class ReplaceCommand(EditorCommand):92 """Command to replace text"""93
94 def __init__(self, document: Document, start: int, end: int, new_text: str):95 self._document = document96 self._start = start97 self._end = end98 self._new_text = new_text99 self._old_text: str = "" # Store for undo100
101 def execute(self) -> None:102 # Store old text and replace103 self._old_text = self._document.delete(self._start, self._end)104 self._document.insert(self._start, self._new_text)105
106 def undo(self) -> None:107 # Delete new text and restore old108 self._document.delete(self._start, self._start + len(self._new_text))109 self._document.insert(self._start, self._old_text)110
111 @property112 def description(self) -> str:113 return f"Replace '{self._old_text}' with '{self._new_text}'"114
115# Step 4: Create Macro Command (Composite Command)116class MacroCommand(EditorCommand):117 """Composite command - executes multiple commands"""118
119 def __init__(self, commands: List[EditorCommand], name: str = "Macro"):120 self._commands = commands121 self._name = name122
123 def execute(self) -> None:124 for command in self._commands:125 command.execute()126
127 def undo(self) -> None:128 # Undo in reverse order!129 for command in reversed(self._commands):130 command.undo()131
132 @property133 def description(self) -> str:134 return f"Macro: {self._name} ({len(self._commands)} commands)"135
136# Step 5: Create the Editor (Invoker)137class TextEditor:138 """Invoker - manages command execution and undo/redo"""139
140 def __init__(self):141 self._document = Document()142 self._undo_stack: List[EditorCommand] = []143 self._redo_stack: List[EditorCommand] = []144
145 def execute_command(self, command: EditorCommand) -> None:146 """Execute a command and add to history"""147 command.execute()148 self._undo_stack.append(command)149 self._redo_stack.clear() # Clear redo stack on new command150 print(f"✅ Executed: {command.description}")151 print(f" Content: '{self._document.get_content()}'")152
153 def undo(self) -> bool:154 """Undo the last command"""155 if not self._undo_stack:156 print("⚠️ Nothing to undo")157 return False158
159 command = self._undo_stack.pop()160 command.undo()161 self._redo_stack.append(command)162 print(f"⏪ Undone: {command.description}")163 print(f" Content: '{self._document.get_content()}'")164 return True165
166 def redo(self) -> bool:167 """Redo the last undone command"""168 if not self._redo_stack:169 print("⚠️ Nothing to redo")170 return False171
172 command = self._redo_stack.pop()173 command.execute()174 self._undo_stack.append(command)175 print(f"⏩ Redone: {command.description}")176 print(f" Content: '{self._document.get_content()}'")177 return True178
179 def get_content(self) -> str:180 """Get document content"""181 return self._document.get_content()182
183 def get_document(self) -> Document:184 """Get document for creating commands"""185 return self._document186
187 def get_history(self) -> List[str]:188 """Get command history"""189 return [cmd.description for cmd in self._undo_stack]190
191# Step 6: Use the pattern192def main():193 # Create editor194 editor = TextEditor()195 doc = editor.get_document()196
197 print("=" * 50)198 print("Text Editor with Undo/Redo (Command Pattern)")199 print("=" * 50)200
201 # Type some text202 editor.execute_command(TypeCommand(doc, "Hello", 0))203 editor.execute_command(TypeCommand(doc, " World", 5))204 editor.execute_command(TypeCommand(doc, "!", 11))205
206 # Delete some text207 editor.execute_command(DeleteCommand(doc, 5, 11)) # Delete " World"208
209 # Replace text210 editor.execute_command(ReplaceCommand(doc, 0, 5, "Hi"))211
212 print("\n" + "-" * 50)213 print("Testing Undo/Redo")214 print("-" * 50)215
216 # Undo operations217 editor.undo() # Undo replace218 editor.undo() # Undo delete219 editor.undo() # Undo "!"220
221 # Redo some operations222 editor.redo() # Redo "!"223 editor.redo() # Redo delete224
225 print("\n" + "-" * 50)226 print("Testing Macro Command")227 print("-" * 50)228
229 # Create a macro command230 doc = editor.get_document()231 macro = MacroCommand([232 TypeCommand(doc, " - Edited", editor.get_document().get_length()),233 TypeCommand(doc, " (v2)", editor.get_document().get_length() + 9),234 ], "Add version tag")235
236 editor.execute_command(macro)237
238 # Undo entire macro at once239 editor.undo()240
241 print("\n" + "-" * 50)242 print("Command History:")243 print("-" * 50)244 for i, desc in enumerate(editor.get_history(), 1):245 print(f" {i}. {desc}")246
247 print("\n✅ Command Pattern: Full undo/redo with command history!")248
249if __name__ == "__main__":250 main()1import java.util.*;2
3// Step 1: Create the Document (Receiver)4class Document {5 /**6 * Receiver - the document being edited7 */8 private StringBuilder content = new StringBuilder();9
10 public void insert(int position, String text) {11 // Insert text at position12 content.insert(position, text);13 }14
15 public String delete(int start, int end) {16 // Delete text in range and return deleted text17 String deleted = content.substring(start, end);18 content.delete(start, end);19 return deleted;20 }21
22 public String getContent() {23 // Get document content24 return content.toString();25 }26
27 public int getLength() {28 // Get document length29 return content.length();30 }31}32
33// Step 2: Define the Command interface34interface EditorCommand {35 /**36 * Command interface for editor operations37 */38 void execute();39 void undo();40 String getDescription();41}42
43// Step 3: Create Concrete Commands44class TypeCommand implements EditorCommand {45 /**46 * Command to type/insert text47 */48 private Document document;49 private String text;50 private int position;51
52 public TypeCommand(Document document, String text, int position) {53 this.document = document;54 this.text = text;55 this.position = position;56 }57
58 @Override59 public void execute() {60 document.insert(position, text);61 }62
63 @Override64 public void undo() {65 // Delete the text that was typed66 document.delete(position, position + text.length());67 }68
69 @Override70 public String getDescription() {71 return "Type '" + text + "' at position " + position;72 }73}74
75class DeleteCommand implements EditorCommand {76 /**77 * Command to delete text78 */79 private Document document;80 private int start;81 private int end;82 private String deletedText = ""; // Store for undo83
84 public DeleteCommand(Document document, int start, int end) {85 this.document = document;86 this.start = start;87 this.end = end;88 }89
90 @Override91 public void execute() {92 // Store deleted text for undo93 deletedText = document.delete(start, end);94 }95
96 @Override97 public void undo() {98 // Re-insert the deleted text99 document.insert(start, deletedText);100 }101
102 @Override103 public String getDescription() {104 return "Delete text from " + start + " to " + end;105 }106}107
108class ReplaceCommand implements EditorCommand {109 /**110 * Command to replace text111 */112 private Document document;113 private int start;114 private int end;115 private String newText;116 private String oldText = ""; // Store for undo117
118 public ReplaceCommand(Document document, int start, int end, String newText) {119 this.document = document;120 this.start = start;121 this.end = end;122 this.newText = newText;123 }124
125 @Override126 public void execute() {127 // Store old text and replace128 oldText = document.delete(start, end);129 document.insert(start, newText);130 }131
132 @Override133 public void undo() {134 // Delete new text and restore old135 document.delete(start, start + newText.length());136 document.insert(start, oldText);137 }138
139 @Override140 public String getDescription() {141 return "Replace '" + oldText + "' with '" + newText + "'";142 }143}144
145// Step 4: Create Macro Command (Composite Command)146class MacroCommand implements EditorCommand {147 /**148 * Composite command - executes multiple commands149 */150 private List<EditorCommand> commands;151 private String name;152
153 public MacroCommand(List<EditorCommand> commands, String name) {154 this.commands = commands;155 this.name = name;156 }157
158 @Override159 public void execute() {160 for (EditorCommand command : commands) {161 command.execute();162 }163 }164
165 @Override166 public void undo() {167 // Undo in reverse order!168 for (int i = commands.size() - 1; i >= 0; i--) {169 commands.get(i).undo();170 }171 }172
173 @Override174 public String getDescription() {175 return "Macro: " + name + " (" + commands.size() + " commands)";176 }177}178
179// Step 5: Create the Editor (Invoker)180class TextEditor {181 /**182 * Invoker - manages command execution and undo/redo183 */184 private Document document;185 private List<EditorCommand> undoStack;186 private List<EditorCommand> redoStack;187
188 public TextEditor() {189 document = new Document();190 undoStack = new ArrayList<>();191 redoStack = new ArrayList<>();192 }193
194 public void executeCommand(EditorCommand command) {195 // Execute a command and add to history196 command.execute();197 undoStack.add(command);198 redoStack.clear(); // Clear redo stack on new command199 System.out.println("✅ Executed: " + command.getDescription());200 System.out.println(" Content: '" + document.getContent() + "'");201 }202
203 public boolean undo() {204 // Undo the last command205 if (undoStack.isEmpty()) {206 System.out.println("⚠️ Nothing to undo");207 return false;208 }209
210 EditorCommand command = undoStack.remove(undoStack.size() - 1);211 command.undo();212 redoStack.add(command);213 System.out.println("⏪ Undone: " + command.getDescription());214 System.out.println(" Content: '" + document.getContent() + "'");215 return true;216 }217
218 public boolean redo() {219 // Redo the last undone command220 if (redoStack.isEmpty()) {221 System.out.println("⚠️ Nothing to redo");222 return false;223 }224
225 EditorCommand command = redoStack.remove(redoStack.size() - 1);226 command.execute();227 undoStack.add(command);228 System.out.println("⏩ Redone: " + command.getDescription());229 System.out.println(" Content: '" + document.getContent() + "'");230 return true;231 }232
233 public String getContent() {234 return document.getContent();235 }236
237 public Document getDocument() {238 return document;239 }240
241 public List<String> getHistory() {242 List<String> history = new ArrayList<>();243 for (EditorCommand cmd : undoStack) {244 history.add(cmd.getDescription());245 }246 return history;247 }248}249
250// Step 6: Use the pattern251public class Main {252 public static void main(String[] args) {253 // Create editor254 TextEditor editor = new TextEditor();255 Document doc = editor.getDocument();256
257 System.out.println("=".repeat(50));258 System.out.println("Text Editor with Undo/Redo (Command Pattern)");259 System.out.println("=".repeat(50));260
261 // Type some text262 editor.executeCommand(new TypeCommand(doc, "Hello", 0));263 editor.executeCommand(new TypeCommand(doc, " World", 5));264 editor.executeCommand(new TypeCommand(doc, "!", 11));265
266 // Delete some text267 editor.executeCommand(new DeleteCommand(doc, 5, 11)); // Delete " World"268
269 // Replace text270 editor.executeCommand(new ReplaceCommand(doc, 0, 5, "Hi"));271
272 System.out.println("\n" + "-".repeat(50));273 System.out.println("Testing Undo/Redo");274 System.out.println("-".repeat(50));275
276 // Undo operations277 editor.undo(); // Undo replace278 editor.undo(); // Undo delete279 editor.undo(); // Undo "!"280
281 // Redo some operations282 editor.redo(); // Redo "!"283 editor.redo(); // Redo delete284
285 System.out.println("\n" + "-".repeat(50));286 System.out.println("Testing Macro Command");287 System.out.println("-".repeat(50));288
289 // Create a macro command290 doc = editor.getDocument();291 int length = doc.getLength();292 MacroCommand macro = new MacroCommand(Arrays.asList(293 new TypeCommand(doc, " - Edited", length),294 new TypeCommand(doc, " (v2)", length + 9)295 ), "Add version tag");296
297 editor.executeCommand(macro);298
299 // Undo entire macro at once300 editor.undo();301
302 System.out.println("\n" + "-".repeat(50));303 System.out.println("Command History:");304 System.out.println("-".repeat(50));305 int i = 1;306 for (String desc : editor.getHistory()) {307 System.out.println(" " + i++ + ". " + desc);308 }309
310 System.out.println("\n✅ Command Pattern: Full undo/redo with command history!");311 }312}Command Pattern Variants
Section titled “Command Pattern Variants”There are different ways to implement the Command Pattern:
1. Simple Command (No Undo)
Section titled “1. Simple Command (No Undo)”When undo isn’t needed:
1# Simple Command - no undo needed2from abc import ABC, abstractmethod3
4class Command(ABC):5 @abstractmethod6 def execute(self): pass7
8class PrintCommand(Command):9 def __init__(self, message: str):10 self.message = message11
12 def execute(self):13 print(self.message)14
15# Usage16cmd = PrintCommand("Hello!")17cmd.execute()1// Simple Command - no undo needed2interface Command {3 void execute();4}5
6class PrintCommand implements Command {7 private String message;8
9 public PrintCommand(String message) {10 this.message = message;11 }12
13 @Override14 public void execute() {15 System.out.println(message);16 }17}18
19// Usage20Command cmd = new PrintCommand("Hello!");21cmd.execute();Pros: Simple, lightweight
Cons: No undo support
2. Command with Callback
Section titled “2. Command with Callback”Using callbacks for results:
1# Command with callback2from typing import Callable, Any3
4class AsyncCommand:5 def __init__(self, action: Callable, callback: Callable[[Any], None]):6 self.action = action7 self.callback = callback8
9 def execute(self):10 result = self.action()11 self.callback(result)12
13# Usage14def fetch_data():15 return {"users": [1, 2, 3]}16
17def handle_result(data):18 print(f"Got data: {data}")19
20cmd = AsyncCommand(fetch_data, handle_result)21cmd.execute()1// Command with callback2import java.util.function.Consumer;3import java.util.function.Supplier;4
5class AsyncCommand<T> {6 private Supplier<T> action;7 private Consumer<T> callback;8
9 public AsyncCommand(Supplier<T> action, Consumer<T> callback) {10 this.action = action;11 this.callback = callback;12 }13
14 public void execute() {15 T result = action.get();16 callback.accept(result);17 }18}19
20// Usage21AsyncCommand<String> cmd = new AsyncCommand<>(22 () -> "Hello from async!",23 result -> System.out.println("Got: " + result)24);25cmd.execute();Pros: Supports async operations
Cons: More complex
3. Command Queue
Section titled “3. Command Queue”Queuing commands for batch execution:
1# Command Queue - batch execution2from collections import deque3from abc import ABC, abstractmethod4
5class Command(ABC):6 @abstractmethod7 def execute(self): pass8
9class CommandQueue:10 def __init__(self):11 self._queue = deque()12
13 def add(self, command: Command):14 self._queue.append(command)15
16 def execute_all(self):17 while self._queue:18 command = self._queue.popleft()19 command.execute()20
21# Usage22queue = CommandQueue()23queue.add(PrintCommand("First"))24queue.add(PrintCommand("Second"))25queue.add(PrintCommand("Third"))26queue.execute_all() # Executes all in order1// Command Queue - batch execution2import java.util.*;3
4class CommandQueue {5 private Queue<Command> queue = new LinkedList<>();6
7 public void add(Command command) {8 queue.add(command);9 }10
11 public void executeAll() {12 while (!queue.isEmpty()) {13 Command command = queue.poll();14 command.execute();15 }16 }17}18
19// Usage20CommandQueue queue = new CommandQueue();21queue.add(new PrintCommand("First"));22queue.add(new PrintCommand("Second"));23queue.add(new PrintCommand("Third"));24queue.executeAll(); // Executes all in orderWhen to Use Command Pattern?
Section titled “When to Use Command Pattern?”Use Command Pattern when:
✅ You need undo/redo - Commands know how to reverse themselves
✅ You want to queue operations - Execute later or in sequence
✅ You need operation logging - Commands can be serialized and logged
✅ You want to decouple - Invoker doesn’t know about receiver
✅ You need transactional behavior - Rollback if something fails
When NOT to Use Command Pattern?
Section titled “When NOT to Use Command Pattern?”Don’t use Command Pattern when:
❌ Simple operations - Direct method calls are clearer
❌ No undo needed - Overhead isn’t justified
❌ No operation history - Don’t need logging or auditing
❌ Performance critical - Command objects add overhead
❌ Over-engineering - Don’t add complexity for simple cases
Common Mistakes to Avoid
Section titled “Common Mistakes to Avoid”Mistake 1: Commands That Don’t Store State for Undo
Section titled “Mistake 1: Commands That Don’t Store State for Undo”1# ❌ Bad: Command doesn't store state for undo2class BadDeleteCommand:3 def __init__(self, document, start, end):4 self.document = document5 self.start = start6 self.end = end7 # Missing: self.deleted_text!8
9 def execute(self):10 self.document.delete(self.start, self.end)11
12 def undo(self):13 # Problem: What text to restore? We don't know!14 pass15
16# ✅ Good: Command stores state for undo17class GoodDeleteCommand:18 def __init__(self, document, start, end):19 self.document = document20 self.start = start21 self.end = end22 self.deleted_text = "" # Will store deleted text23
24 def execute(self):25 self.deleted_text = self.document.delete(self.start, self.end)26
27 def undo(self):28 self.document.insert(self.start, self.deleted_text)1// ❌ Bad: Command doesn't store state for undo2class BadDeleteCommand implements Command {3 private Document document;4 private int start, end;5 // Missing: deletedText!6
7 @Override8 public void execute() {9 document.delete(start, end);10 }11
12 @Override13 public void undo() {14 // Problem: What text to restore? We don't know!15 }16}17
18// ✅ Good: Command stores state for undo19class GoodDeleteCommand implements Command {20 private Document document;21 private int start, end;22 private String deletedText = ""; // Will store deleted text23
24 @Override25 public void execute() {26 deletedText = document.delete(start, end);27 }28
29 @Override30 public void undo() {31 document.insert(start, deletedText);32 }33}Mistake 2: Commands That Modify External State
Section titled “Mistake 2: Commands That Modify External State”1# ❌ Bad: Command modifies external/global state2global_counter = 03
4class BadCommand:5 def execute(self):6 global global_counter7 global_counter += 1 # Bad: Modifying global state!8
9 def undo(self):10 global global_counter11 global_counter -= 1 # Problem: Race conditions!12
13# ✅ Good: Command only modifies controlled receiver14class GoodCommand:15 def __init__(self, counter):16 self.counter = counter # Controlled receiver17 self.prev_value = 018
19 def execute(self):20 self.prev_value = self.counter.get_value()21 self.counter.increment()22
23 def undo(self):24 self.counter.set_value(self.prev_value)1// ❌ Bad: Command modifies external/global state2class BadCommand implements Command {3 private static int globalCounter = 0; // Static/global state!4
5 @Override6 public void execute() {7 globalCounter++; // Bad: Modifying global state!8 }9
10 @Override11 public void undo() {12 globalCounter--; // Problem: Race conditions!13 }14}15
16// ✅ Good: Command only modifies controlled receiver17class GoodCommand implements Command {18 private Counter counter; // Controlled receiver19 private int prevValue;20
21 public GoodCommand(Counter counter) {22 this.counter = counter;23 }24
25 @Override26 public void execute() {27 prevValue = counter.getValue();28 counter.increment();29 }30
31 @Override32 public void undo() {33 counter.setValue(prevValue);34 }35}Mistake 3: Not Clearing Redo Stack on New Command
Section titled “Mistake 3: Not Clearing Redo Stack on New Command”1# ❌ Bad: Not clearing redo stack2class BadEditor:3 def __init__(self):4 self.undo_stack = []5 self.redo_stack = []6
7 def execute(self, command):8 command.execute()9 self.undo_stack.append(command)10 # Missing: self.redo_stack.clear()!11 # Problem: Redo after new command causes inconsistency!12
13# ✅ Good: Clear redo stack on new command14class GoodEditor:15 def __init__(self):16 self.undo_stack = []17 self.redo_stack = []18
19 def execute(self, command):20 command.execute()21 self.undo_stack.append(command)22 self.redo_stack.clear() # Clear redo stack!1// ❌ Bad: Not clearing redo stack2class BadEditor {3 private List<Command> undoStack = new ArrayList<>();4 private List<Command> redoStack = new ArrayList<>();5
6 public void execute(Command command) {7 command.execute();8 undoStack.add(command);9 // Missing: redoStack.clear()!10 // Problem: Redo after new command causes inconsistency!11 }12}13
14// ✅ Good: Clear redo stack on new command15class GoodEditor {16 private List<Command> undoStack = new ArrayList<>();17 private List<Command> redoStack = new ArrayList<>();18
19 public void execute(Command command) {20 command.execute();21 undoStack.add(command);22 redoStack.clear(); // Clear redo stack!23 }24}Benefits of Command Pattern
Section titled “Benefits of Command Pattern”- Decoupling - Invoker doesn’t know about receiver
- Undo/Redo - Commands know how to reverse themselves
- Queueing - Commands can be queued for later execution
- Logging - Commands can be serialized and logged
- Transactions - Group commands for atomic execution
- Macro Commands - Combine multiple commands into one
Revision: Quick Catch-Up
Section titled “Revision: Quick Catch-Up”What is Command Pattern?
Section titled “What is Command Pattern?”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 a request’s execution, and support undoable operations.
Why Use It?
Section titled “Why Use It?”- ✅ Undo/Redo - Commands know how to reverse
- ✅ Queueing - Execute commands later
- ✅ Logging - Track all operations
- ✅ Decoupling - Invoker doesn’t know receiver
- ✅ Macro commands - Combine operations
How It Works?
Section titled “How It Works?”- Define Command interface - execute() and undo() methods
- Create Concrete Commands - Each wraps a receiver action
- Create Receiver - The object that performs the action
- Create Invoker - Triggers commands, manages history
- Client - Creates commands and configures invoker
Key Components
Section titled “Key Components”1Client → creates → Command → calls → Receiver2 ↓3 Invoker → invokes → Command- Command - Interface with execute() and undo()
- Concrete Command - Wraps receiver and action
- Receiver - Performs the actual work
- Invoker - Triggers commands, stores history
- Client - Creates and configures commands
Simple Example
Section titled “Simple Example”1class Command(ABC):2 @abstractmethod3 def execute(self): pass4
5 @abstractmethod6 def undo(self): pass7
8class LightOnCommand(Command):9 def __init__(self, light):10 self.light = light11
12 def execute(self):13 self.light.turn_on()14
15 def undo(self):16 self.light.turn_off()17
18class RemoteControl:19 def __init__(self):20 self.history = []21
22 def execute(self, command):23 command.execute()24 self.history.append(command)25
26 def undo(self):27 if self.history:28 self.history.pop().undo()When to Use?
Section titled “When to Use?”✅ Need undo/redo functionality
✅ Need to queue operations
✅ Need to log all operations
✅ Need to decouple invoker from receiver
✅ Need transactional behavior
When NOT to Use?
Section titled “When NOT to Use?”❌ Simple operations
❌ No undo needed
❌ No operation history needed
❌ Over-engineering simple cases
Key Takeaways
Section titled “Key Takeaways”- Command Pattern = Operations as objects
- Command = Encapsulates action and undo
- Invoker = Triggers commands, manages history
- Receiver = Performs actual work
- Benefit = Undo, queueing, logging, decoupling
Common Pattern Structure
Section titled “Common Pattern Structure”1# 1. Command Interface2class Command(ABC):3 @abstractmethod4 def execute(self): pass5 @abstractmethod6 def undo(self): pass7
8# 2. Concrete Command9class ConcreteCommand(Command):10 def __init__(self, receiver):11 self.receiver = receiver12 self.prev_state = None13
14 def execute(self):15 self.prev_state = self.receiver.get_state()16 self.receiver.action()17
18 def undo(self):19 self.receiver.set_state(self.prev_state)20
21# 3. Invoker22class Invoker:23 def __init__(self):24 self.history = []25
26 def execute(self, command):27 command.execute()28 self.history.append(command)29
30 def undo(self):31 if self.history:32 self.history.pop().undo()Remember
Section titled “Remember”- Command Pattern encapsulates operations as objects
- It enables undo/redo by storing state
- It supports queueing and logging operations
- Use it when you need operation history
- Don’t use it for simple direct calls!
Interview Focus: Command Pattern
Section titled “Interview Focus: Command Pattern”Key Points to Remember
Section titled “Key Points to Remember”1. Core Concept
Section titled “1. Core Concept”What to say:
“Command Pattern is a behavioral design pattern that encapsulates a request as an object. This allows you to parameterize clients with different requests, queue operations, log them, and support undoable operations. The command object contains all information needed to perform an action.”
Why it matters:
- Shows you understand the fundamental purpose
- Demonstrates knowledge of encapsulation
- Indicates you can explain concepts clearly
2. When to Use Command Pattern
Section titled “2. When to Use Command Pattern”Must mention:
- ✅ Undo/Redo - Each command knows how to reverse itself
- ✅ Operation queueing - Execute later or in batch
- ✅ Logging/Auditing - Commands can be serialized
- ✅ Decoupling - Invoker doesn’t know about receiver
- ✅ Transactional behavior - Rollback on failure
Example scenario to give:
“I’d use Command Pattern when building a text editor. Each operation - typing, deleting, formatting - is a command object. This makes undo/redo trivial: just pop from undo stack, call undo(), push to redo stack. We can also log all commands for crash recovery or collaboration features.”
Must discuss:
- Command - Encapsulates an operation with its state, supports undo
- Strategy - Encapsulates an algorithm, algorithms are interchangeable
- Key difference - Command has state and undo, Strategy is stateless
Example to give:
“Command Pattern encapsulates an operation - ‘delete text at position 5’ - and knows how to undo it. Strategy Pattern encapsulates an algorithm - ‘sort using QuickSort’ - but doesn’t track state. Commands remember what they did so they can undo it; strategies just execute algorithms.”
4. Implementing Undo/Redo
Section titled “4. Implementing Undo/Redo”Must explain:
- Undo Stack - Stores executed commands
- Redo Stack - Stores undone commands
- Execute - Execute command, push to undo stack, clear redo stack
- Undo - Pop from undo, call undo(), push to redo
- Redo - Pop from redo, call execute(), push to undo
Code to have ready:
1class Editor:2 def __init__(self):3 self.undo_stack = []4 self.redo_stack = []5
6 def execute(self, command):7 command.execute()8 self.undo_stack.append(command)9 self.redo_stack.clear() # Important!10
11 def undo(self):12 if self.undo_stack:13 cmd = self.undo_stack.pop()14 cmd.undo()15 self.redo_stack.append(cmd)16
17 def redo(self):18 if self.redo_stack:19 cmd = self.redo_stack.pop()20 cmd.execute()21 self.undo_stack.append(cmd)5. Benefits and Trade-offs
Section titled “5. Benefits and Trade-offs”Benefits to mention:
- Decoupling - Invoker doesn’t know about receiver
- Undo/Redo - Commands are reversible
- Queueing - Commands can be queued
- Logging - Commands can be serialized
- Macro Commands - Combine multiple commands
Trade-offs to acknowledge:
- Complexity - More classes than direct calls
- Memory - Commands store state for undo
- Overhead - Command objects have cost
- Overkill for simple cases - Direct calls are simpler
6. Common Interview Questions
Section titled “6. Common Interview Questions”Q: “How would you implement undo in a text editor?”
A:
“I’d use Command Pattern. Each operation - TypeCommand, DeleteCommand, FormatCommand - stores the state needed to undo itself. DeleteCommand stores the deleted text. When execute() is called, it deletes and stores what was deleted. When undo() is called, it restores the deleted text. The editor maintains undo/redo stacks to track command history.”
Q: “How does Command Pattern support transactions?”
A:
“You can create a TransactionCommand that holds multiple commands. On execute(), it executes all commands in sequence. If any fails, it calls undo() on all previously executed commands in reverse order. This gives atomic, all-or-nothing behavior - either all commands succeed or all are rolled back.”
Q: “How does Command Pattern relate to SOLID principles?”
A:
“Command Pattern supports Single Responsibility - each command handles one operation. It supports Open/Closed - add new commands without modifying invoker. It supports Dependency Inversion - invoker depends on Command interface, not concrete commands. It also supports Interface Segregation - Command interface is focused (execute, undo).”
Interview Checklist
Section titled “Interview Checklist”Before your interview, make sure you can:
- Define Command Pattern clearly in one sentence
- Explain when to use it (undo, queueing, logging)
- Describe the structure: Command, Invoker, Receiver
- Implement undo/redo from scratch
- Compare with Strategy Pattern
- List benefits and trade-offs
- Connect to SOLID principles
- Identify when NOT to use it
- Give 2-3 real-world examples (editors, transactions, GUI)
- Discuss Macro Commands (composite)
Remember: Command Pattern is about encapsulating operations as objects - enabling undo, queueing, and logging with full control over execution! 🎮