The Command Pattern
The Command Pattern
The Command Pattern encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations. Each command object knows how to execute itself and optionally how to reverse that execution. This decouples the invoker (who triggers actions) from the receiver (who performs them).
Why Command Matters
A text editor needs to support undo and redo for every action - typing, deleting, copying, and formatting. Without Command you would have to track every operation manually with complex conditional logic. With Command each action is its own object with execute() and undo() methods. The editor pushes commands onto a stack, and undo simply pops and reverses.
Class Diagram
+------------------+ +------------------+
| Invoker | | Receiver |
| (TextEditor) | | (Document) |
+------------------+ +------------------+
| - history[] | | + insertText(t) |
| - command | | + deleteText(t) |
+------------------+ +------------------+
| + execute(c) | +--------+---------+
| + undo() | |
+--------+---------+ |
| |
| calls |
v |
+--------+---------+ |
| Command | <<interface>> |
+------------------+ |
| + execute() | |
| + undo() | |
+--------+---------+ |
| |
+--------+----------------+ |
| | | |
+---+-----------+ +---+-----+ +---+----------+
| InsertCommand | |DeleteCmd| | FormatCmd |
+---------------+ +---------+ +-------------+
Code Example
Define the Command interface and the receiver class that does the actual work:
public interface Command {
void execute();
void undo();
}
public class Document {
private StringBuilder text = new StringBuilder();
public void insertText(String content) { text.append(content); }
public void deleteText(int length) {
int start = text.length() - length;
text.delete(start, text.length());
}
public String getText() { return text.toString(); }
}
Implement an InsertCommand and an Invoker that maintains the undo stack:
public class InsertCommand implements Command {
private Document doc;
private String text;
public InsertCommand(Document doc, String text) {
this.doc = doc;
this.text = text;
}
public void execute() { doc.insertText(text); }
public void undo() { doc.deleteText(text.length()); }
}
public class TextEditor {
private Deque<Command> history = new ArrayDeque<>();
public void executeCommand(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undo() {
if (!history.isEmpty()) {
history.pop().undo();
}
}
}
The editor works with any command without knowing the details:
Document doc = new Document();
TextEditor editor = new TextEditor();
editor.executeCommand(new InsertCommand(doc, "Hello "));
editor.executeCommand(new InsertCommand(doc, "World"));
System.out.println(doc.getText()); // Hello World
editor.undo();
System.out.println(doc.getText()); // Hello
Beyond Undo/Redo
Commands can be queued and executed later, which is useful for task schedulers and job queues. They can also be combined into macros - a MacroCommand holds a list of commands and executes them all in sequence. Logging every command also enables replay and debugging of user actions.
Real-World Use
IDEs use Command for every edit operation. Database frameworks wrap SQL as command objects for batch execution. GUI buttons, menu items, and keyboard shortcuts all map to command objects, making it trivial to reassign actions. Transactional systems use commands to support rollback on failure.
Key Points
- Each command encapsulates a request as an object with execute and undo methods.
- The invoker does not know what action it triggers - it only calls execute.
- Commands enable undo/redo by maintaining an execution history stack.
- Commands can be queued, logged, and composed into macros.
- This pattern separates the what from the how, improving flexibility and testability.