Command Pattern explained simply
What if you could turn every action in your application into an object? Something you could store, queue up, undo, log, and replay whenever you want. That is exactly what the Command Pattern does.
What is the Command Pattern?
- Encapsulates a request as an object, containing all the information needed to perform the action.
- Decouples the sender of a request from the receiver that actually does the work.
- Enables undo/redo, queuing, logging, and macro recording out of the box.
In short:
Turn actions into objects.
Real-Life Analogy
Think about how a restaurant works. You sit down, and the waiter takes your order. But the waiter doesn’t cook anything. Instead, they write your order on a slip of paper and pass it to the kitchen.
That order slip is the command. It contains everything the kitchen needs to know – what dish, how you want it cooked, any modifications. The waiter (invoker) doesn’t need to know how to cook. The kitchen (receiver) doesn’t need to interact with you directly. And that slip can be queued, prioritized, or even cancelled before the kitchen gets to it.
That separation is exactly what the Command Pattern gives you in code.
Structure
- Command (Interface): Declares
execute()andundo()methods. Every concrete command implements this. - InsertCommand / DeleteCommand (Concrete Commands): Each one knows exactly how to perform a specific action on the receiver, and how to reverse it.
- CommandHistory (Invoker): Doesn’t know what the commands actually do. It just calls
execute()and keeps a stack so it can callundo()later. - TextEditor (Receiver): The object that actually does the real work – inserting and deleting text.
The invoker never talks to the receiver directly. It only talks through command objects. That is the entire point.
Example in Java – Text Editor with Undo/Redo
Text editors are the classic example here. Every keystroke, every paste, every delete is a command. And you need to undo any of them at any time. Let’s build it.
Command Interface
public interface Command {
void execute();
void undo();
}
Two methods. That is all a command needs. Do something, and know how to reverse it.
TextEditor Class (Receiver)
public class TextEditor {
private final StringBuilder content = new StringBuilder();
public void insert(int position, String text) {
content.insert(position, text);
}
public String delete(int position, int length) {
String deleted = content.substring(position, position + length);
content.delete(position, position + length);
return deleted;
}
public String getContent() {
return content.toString();
}
}
The receiver is straightforward. It inserts text, deletes text, and returns the current content. It has no idea that commands exist.
InsertCommand
public class InsertCommand implements Command {
private final TextEditor editor;
private final String text;
private final int position;
public InsertCommand(TextEditor editor, int position, String text) {
this.editor = editor;
this.position = position;
this.text = text;
}
@Override
public void execute() {
editor.insert(position, text);
}
@Override
public void undo() {
editor.delete(position, text.length());
}
}
Inserting text is easy to undo – you just delete the same number of characters from the same position.
DeleteCommand
public class DeleteCommand implements Command {
private final TextEditor editor;
private final int position;
private final int length;
private String deletedText;
public DeleteCommand(TextEditor editor, int position, int length) {
this.editor = editor;
this.position = position;
this.length = length;
}
@Override
public void execute() {
deletedText = editor.delete(position, length);
}
@Override
public void undo() {
editor.insert(position, deletedText);
}
}
This is where it gets interesting. The DeleteCommand saves the deleted text during execute() so it can restore it on undo(). The command object carries its own state.
CommandHistory (Invoker)
import java.util.Stack;
public class CommandHistory {
private final Stack<Command> history = new Stack<>();
public void executeCommand(Command cmd) {
cmd.execute();
history.push(cmd);
}
public void undo() {
if (!history.isEmpty()) {
Command cmd = history.pop();
cmd.undo();
}
}
}
The invoker has no clue whether it is running an insert, a delete, or anything else. It just calls execute(), pushes to the stack, and pops when you want to undo. Dead simple.
Putting It All Together
public class MainProgram {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandHistory history = new CommandHistory();
// insert "Hello"
history.executeCommand(new InsertCommand(editor, 0, "Hello"));
System.out.println(editor.getContent()); // Hello
// insert " World"
history.executeCommand(new InsertCommand(editor, 5, " World"));
System.out.println(editor.getContent()); // Hello World
// delete "World"
history.executeCommand(new DeleteCommand(editor, 6, 5));
System.out.println(editor.getContent()); // Hello
// undo the delete
history.undo();
System.out.println(editor.getContent()); // Hello World
// undo the second insert
history.undo();
System.out.println(editor.getContent()); // Hello
}
}
Output:
Hello
Hello World
Hello
Hello World
Hello
Each action is an object. Each object knows how to do and undo itself. The history stack just orchestrates the sequence. You could extend this with redo by adding a second stack, and the pattern stays clean.
Key Components
| Component | Role | In Our Example |
|---|---|---|
| Command | Declares the execution interface | Command interface |
| Concrete Command | Implements a specific action and its reverse | InsertCommand, DeleteCommand |
| Invoker | Asks the command to carry out the request | CommandHistory |
| Receiver | Knows how to perform the actual work | TextEditor |
When to Use the Command Pattern
- You need undo/redo functionality.
- You want to queue operations and execute them later.
- You need to log operations for auditing or replay.
- You want to support macro recording – a sequence of commands bundled together.
- You need to decouple the object that invokes an operation from the one that performs it.
Advantages
- Undo/Redo becomes trivial – each command knows how to reverse itself.
- Loose coupling – the invoker and receiver don’t know about each other.
- Composability – you can combine simple commands into macro commands.
- Extensibility – adding a new command means writing one class. No changes to existing code.
Disadvantages
- Class explosion – every distinct action needs its own command class.
- Complexity – for simple operations, wrapping everything in command objects can feel like overkill.
- State management – commands that need to store state for undo can get tricky in complex scenarios.
Real-World Use Cases
- Java Runnable: The
Runnableinterface is essentially the Command Pattern. You pass an action as an object to a thread pool. - Transaction Systems: Database transactions wrap operations as commands that can be committed or rolled back.
- Task Queues: Job queues in systems like Celery or Sidekiq treat each job as a serialized command object.
- Macro Recording: Applications like Photoshop or Excel record sequences of commands and replay them.
- Git: Every
git commit,git revert,git cherry-pickis a command object that can be composed, reordered, and undone. - GUI Frameworks: Menu items, toolbar buttons, and keyboard shortcuts all map to command objects – which is why the same action works from three different places.
Wrapping Up
The Command Pattern turns actions into first-class objects. Once you do that, you unlock undo, redo, queuing, logging, and macro recording almost for free. The text editor example shows the core idea – every edit is an object that knows how to execute and reverse itself, and the history stack handles the rest.
If you ever find yourself needing to track, queue, or reverse operations in your application, the Command Pattern is exactly what you are looking for.