Command Pattern
🎯 Explain Like I'm 5...
Imagine you're at a restaurant and you want to order food! 🍕🍔
😟 Without Commands:
- • You tell the waiter what you want
- • The waiter has to remember everything in their head
- • Hard to change orders, no way to undo!
- • Kitchen doesn't have a record of what to make
💡 The Solution - Use Order Slips (Commands)!
- • You tell the waiter your order 📝
- • Waiter writes it on a paper slip (the COMMAND!)
- • The slip goes to the kitchen
- • Kitchen reads the slip and makes your food
- • The slip can be saved, copied, or even undone! 🎉
🌟 The Key Idea:
A command is like a REQUEST written on paper! You can pass it around, save it, queue it up, and even UNDO it later! The order slip doesn't make the food - it just tells the kitchen WHAT to make! 📋
🚀 Why Is This Pattern Useful?
- ✨Undo/Redo operations - Save commands and reverse them!
- ✨Queue operations - Save commands and execute them later!
- ✨Log operations - Keep track of what happened!
- ✨Macro commands - Combine multiple commands into one!
📋 Pattern Purpose
The Command pattern encapsulates a REQUEST as an OBJECT, allowing you to parameterize clients with different requests, queue or log requests, and support UNDOABLE operations. It decouples the object that invokes the operation from the one that knows how to perform it.
⚡ When to Use Command Pattern
- ✓You need to support undo/redo operations
- ✓You want to queue, schedule, or log operations
- ✓You need to parameterize objects with operations
- ✓You want to support macro commands (combining multiple operations)
- ✓You need to decouple the object making requests from the object handling them
🧩 Command Pattern Components
- 📦Command: Interface declaring execute() and undo() methods
- 📦ConcreteCommand: Implements Command, binds Receiver with an action
- 📦Receiver: The object that performs the actual work
- 📦Invoker: Asks the command to execute the request
- 📦Client: Creates commands and sets their receiver
💻 Java Implementations
Example 1: Remote Control (TV Commands)
A universal remote control that can execute and undo various TV operations.
// Command Interfacepublic interface Command { void execute(); void undo();}// Receiver - The object that performs the actual workpublic class TV { private boolean isOn = false; private int volume = 0; public void on() { isOn = true; System.out.println("TV is ON"); } public void off() { isOn = false; System.out.println("TV is OFF"); } public void volumeUp() { if (isOn) { volume++; System.out.println("TV volume: " + volume); } } public void volumeDown() { if (isOn && volume > 0) { volume--; System.out.println("TV volume: " + volume); } } public boolean isOn() { return isOn; } public int getVolume() { return volume; }}// Concrete Command - Turn TV Onpublic class TVOnCommand implements Command { private TV tv; public TVOnCommand(TV tv) { this.tv = tv; } @Override public void execute() { tv.on(); } @Override public void undo() { tv.off(); }}// Concrete Command - Turn TV Offpublic class TVOffCommand implements Command { private TV tv; public TVOffCommand(TV tv) { this.tv = tv; } @Override public void execute() { tv.off(); } @Override public void undo() { tv.on(); }}// Concrete Command - Volume Uppublic class VolumeUpCommand implements Command { private TV tv; public VolumeUpCommand(TV tv) { this.tv = tv; } @Override public void execute() { tv.volumeUp(); } @Override public void undo() { tv.volumeDown(); }}// Invoker - Executes commandspublic class RemoteControl { private Command command; private Command lastCommand; public void setCommand(Command command) { this.command = command; } public void pressButton() { command.execute(); lastCommand = command; } public void pressUndo() { if (lastCommand != null) { lastCommand.undo(); } }}// Client codepublic class RemoteDemo { public static void main(String[] args) { // Receiver TV tv = new TV(); // Commands Command tvOn = new TVOnCommand(tv); Command tvOff = new TVOffCommand(tv); Command volUp = new VolumeUpCommand(tv); // Invoker RemoteControl remote = new RemoteControl(); // Turn on TV remote.setCommand(tvOn); remote.pressButton(); // Increase volume remote.setCommand(volUp); remote.pressButton(); remote.pressButton(); // Undo last command remote.pressUndo(); // Turn off TV remote.setCommand(tvOff); remote.pressButton(); // Undo - turns TV back on! remote.pressUndo(); }}Example 2: Text Editor (Copy, Paste, Undo)
Text editor commands that support undo functionality.
// Receiver - The document being editedpublic class TextEditor { private StringBuilder text = new StringBuilder(); private String clipboard = ""; public void write(String content) { text.append(content); System.out.println("Text: " + text); } public void delete(int length) { if (text.length() >= length) { text.delete(text.length() - length, text.length()); System.out.println("Text: " + text); } } public void copy(int length) { if (text.length() >= length) { clipboard = text.substring(text.length() - length); System.out.println("Copied: " + clipboard); } } public void paste() { text.append(clipboard); System.out.println("Text: " + text); } public String getText() { return text.toString(); } public String getClipboard() { return clipboard; }}// Concrete Command - Write textpublic class WriteCommand implements Command { private TextEditor editor; private String content; public WriteCommand(TextEditor editor, String content) { this.editor = editor; this.content = content; } @Override public void execute() { editor.write(content); } @Override public void undo() { editor.delete(content.length()); }}// Concrete Command - Copy textpublic class CopyCommand implements Command { private TextEditor editor; private int length; private String previousClipboard; public CopyCommand(TextEditor editor, int length) { this.editor = editor; this.length = length; } @Override public void execute() { previousClipboard = editor.getClipboard(); editor.copy(length); } @Override public void undo() { // Restore previous clipboard (simplified) System.out.println("Copy undone - clipboard restored"); }}// Concrete Command - Paste textpublic class PasteCommand implements Command { private TextEditor editor; private int pastedLength; public PasteCommand(TextEditor editor) { this.editor = editor; } @Override public void execute() { String clipboard = editor.getClipboard(); pastedLength = clipboard.length(); editor.paste(); } @Override public void undo() { editor.delete(pastedLength); }}// Editor with undo supportimport java.util.Stack;public class EditorDemo { private TextEditor editor = new TextEditor(); private Stack<Command> history = new Stack<>(); public void executeCommand(Command command) { command.execute(); history.push(command); } public void undo() { if (!history.isEmpty()) { Command command = history.pop(); command.undo(); } else { System.out.println("Nothing to undo!"); } } public static void main(String[] args) { EditorDemo demo = new EditorDemo(); // Write "Hello " demo.executeCommand(new WriteCommand(demo.editor, "Hello ")); // Write "World!" demo.executeCommand(new WriteCommand(demo.editor, "World!")); // Copy last 6 characters ("World!") demo.executeCommand(new CopyCommand(demo.editor, 6)); // Paste demo.executeCommand(new PasteCommand(demo.editor)); // Undo paste System.out.println("\n--- UNDO ---"); demo.undo(); // Undo copy System.out.println("\n--- UNDO ---"); demo.undo(); // Undo write System.out.println("\n--- UNDO ---"); demo.undo(); }}Example 3: Smart Home System
Smart home automation with undoable light commands.
// Receiver - Smart Lightpublic class Light { private String location; private boolean isOn = false; private int brightness = 0; // 0-100 public Light(String location) { this.location = location; } public void on() { isOn = true; brightness = 100; System.out.println(location + " light is ON (100% brightness)"); } public void off() { isOn = false; brightness = 0; System.out.println(location + " light is OFF"); } public void dim(int level) { if (isOn && level >= 0 && level <= 100) { brightness = level; System.out.println(location + " light dimmed to " + level + "%"); } } public boolean isOn() { return isOn; } public int getBrightness() { return brightness; }}// Concrete Command - Turn light onpublic class LightOnCommand implements Command { private Light light; private int previousBrightness; public LightOnCommand(Light light) { this.light = light; } @Override public void execute() { previousBrightness = light.getBrightness(); light.on(); } @Override public void undo() { if (previousBrightness == 0) { light.off(); } else { light.dim(previousBrightness); } }}// Concrete Command - Turn light offpublic class LightOffCommand implements Command { private Light light; private int previousBrightness; public LightOffCommand(Light light) { this.light = light; } @Override public void execute() { previousBrightness = light.getBrightness(); light.off(); } @Override public void undo() { if (previousBrightness > 0) { light.on(); light.dim(previousBrightness); } }}// Concrete Command - Dim lightpublic class DimLightCommand implements Command { private Light light; private int level; private int previousBrightness; public DimLightCommand(Light light, int level) { this.light = light; this.level = level; } @Override public void execute() { previousBrightness = light.getBrightness(); if (!light.isOn()) { light.on(); } light.dim(level); } @Override public void undo() { if (previousBrightness == 0) { light.off(); } else { light.dim(previousBrightness); } }}// Macro Command - Execute multiple commandsimport java.util.ArrayList;import java.util.List;public class MacroCommand implements Command { private List<Command> commands; public MacroCommand(List<Command> commands) { this.commands = new ArrayList<>(commands); } @Override public void execute() { System.out.println("--- Executing Macro Command ---"); for (Command command : commands) { command.execute(); } } @Override public void undo() { System.out.println("--- Undoing Macro Command ---"); // Undo in reverse order for (int i = commands.size() - 1; i >= 0; i--) { commands.get(i).undo(); } }}// Smart Home Demo with Macro Commandsimport java.util.Arrays;public class SmartHomeDemo { public static void main(String[] args) { // Receivers Light livingRoom = new Light("Living Room"); Light bedroom = new Light("Bedroom"); Light kitchen = new Light("Kitchen"); // Commands Command livingRoomOn = new LightOnCommand(livingRoom); Command bedroomOn = new LightOnCommand(bedroom); Command kitchenOn = new LightOnCommand(kitchen); Command livingRoomOff = new LightOffCommand(livingRoom); Command bedroomDim = new DimLightCommand(bedroom, 30); // Macro: "Good Night" - Dim bedroom, turn off others MacroCommand goodNight = new MacroCommand(Arrays.asList( bedroomDim, livingRoomOff, kitchenOn // Night light in kitchen )); // Macro: "All On" MacroCommand allOn = new MacroCommand(Arrays.asList( livingRoomOn, bedroomOn, kitchenOn )); // Execute macro commands System.out.println("=== ALL ON ==="); allOn.execute(); System.out.println("\n=== GOOD NIGHT MODE ==="); goodNight.execute(); System.out.println("\n=== UNDO GOOD NIGHT ==="); goodNight.undo(); System.out.println("\n=== UNDO ALL ON ==="); allOn.undo(); }}🌍 Real-World Examples
- 📝Text Editors: Every edit operation is a command with undo
- 🖱️GUI Buttons: Each button action is a command object
- 💳Transaction Systems: Commands represent transactions that can be rolled back
- ⏰Task Schedulers: Commands queued for later execution
- 🎬Macro Recording: Recording and replaying sequences of commands
✅ Benefits
- ✅Undo/Redo: Easy to implement reversible operations
- ✅Decoupling: Separates requester from executor
- ✅Extensibility: Easy to add new commands without changing existing code
- ✅Composite Commands: Can combine commands into macro commands
- ✅Logging: Commands can be logged and replayed
⚠️ Drawbacks
- ⚠️Complexity: Increases number of classes in the system
- ⚠️Memory: Storing command history can consume memory
- ⚠️Overhead: May be overkill for simple operations
🔑 Key Points to Remember
- 1️⃣Command encapsulates a REQUEST as an OBJECT
- 2️⃣Supports undo/redo by storing state or reverse operations
- 3️⃣Invoker doesn't know about the receiver - complete decoupling
- 4️⃣Commands can be queued, logged, and scheduled
- 5️⃣Macro commands combine multiple commands into one
💪 Practice Scenarios
- • Build a drawing application with undo/redo for shapes
- • Create a task queue system that executes commands in order
- • Implement a game with save/replay functionality using commands
- • Design a home automation system with scheduled commands
- • Build a calculator with operation history and undo