Encapsulate a request as an object — enabling undo/redo, queuing, logging, and parameterisation of operations.
Command turns a request into a standalone object. This object contains the action and all information needed to execute it. You can store commands, pass them as arguments, queue them, log them, and reverse them — none of which is possible with a simple method call.
The Invoker triggers execution without knowing anything about the Receiver. The Client creates the Command object connecting Invoker and Receiver.
You're building a text editor with Undo/Redo. When the user types, cuts, or pastes, you need to remember what happened and reverse it. Storing operations as method calls is impossible — you can't "unsee" a method call or put it in a queue.
Command wraps every operation in an object with execute() and undo(). You push executed commands onto a stack. Undo pops and calls undo(). Redo replays from another stack.
execute(). Often also declares undo() for reversible commands.execute() by calling methods on the Receiver.// Command interface with undo support public interface Command { void execute(); void undo(); } // Receiver — knows how to do the actual work public class TextEditor { private StringBuilder text = new StringBuilder(); public void insertText(String s, int pos) { text.insert(pos, s); } public void deleteText(int start, int len) { text.delete(start, start+len); } public String getText() { return text.toString(); } } // ConcreteCommand — Insert operation with undo public class InsertCommand implements Command { private final TextEditor editor; private final String text; private final int position; public InsertCommand(TextEditor e, String text, int pos) { this.editor = e; this.text = text; this.position = pos; } public void execute() { editor.insertText(text, position); } public void undo() { editor.deleteText(position, text.length()); } } // ConcreteCommand — Delete operation with undo public class DeleteCommand implements Command { private final TextEditor editor; private final int start, length; private String backup; // saved for undo public DeleteCommand(TextEditor e, int start, int length) { this.editor=e; this.start=start; this.length=length; } public void execute() { backup = editor.getText().substring(start, start+length); // save editor.deleteText(start, length); } public void undo() { editor.insertText(backup, start); } // restore } // Invoker — maintains command history for undo/redo public class CommandHistory { private final Deque<Command> undoStack = new ArrayDeque<>(); private final Deque<Command> redoStack = new ArrayDeque<>(); public void execute(Command cmd) { cmd.execute(); undoStack.push(cmd); redoStack.clear(); // new action clears redo history } public void undo() { if (!undoStack.isEmpty()) { Command cmd = undoStack.pop(); cmd.undo(); redoStack.push(cmd); } } public void redo() { if (!redoStack.isEmpty()) { Command cmd = redoStack.pop(); cmd.execute(); undoStack.push(cmd); } } } // Client public class Main { public static void main(String[] args) { TextEditor editor = new TextEditor(); CommandHistory history = new CommandHistory(); history.execute(new InsertCommand(editor, "Hello", 0)); history.execute(new InsertCommand(editor, " World", 5)); System.out.println(editor.getText()); // Hello World history.undo(); System.out.println(editor.getText()); // Hello history.redo(); System.out.println(editor.getText()); // Hello World } }
java.lang.Runnable — the canonical Command interface in Java (no undo, but encapsulates work)java.util.concurrent.Callable — Command with a return valueexecute() makes side effects that undo() doesn't reverse — partial state restoration causes corruption on undoDeleteCommand deletes text but forgets to save it in execute() — undo() has nothing to restore fromNoSuchElementExceptionexecute() runs, the object's state has changedexecute() does is snapshot every piece of state it will change — undo() restores from that snapshotexecute() — throw if re-executed to prevent double execution bugs!stack.isEmpty() before popping in undo()/redo() — or use peek() first// ❌ BREAKING — state not saved before execute public class BrokenDeleteCommand implements Command { private String backup; // never populated! public void execute() { editor.deleteText(start, len); } // ❌ no save public void undo() { editor.insertText(backup, start); } // ❌ backup=null } // ✅ FIX — save state in execute() first public void execute() { backup = editor.getText().substring(start, start+len); // ✅ save first editor.deleteText(start, len); } // ❌ BREAKING — double execution corrupts history Command cmd = new InsertCommand(editor, "Hi", 0); history.execute(cmd); history.execute(cmd); // ❌ same object — history has two refs, undo misaligns // ✅ FIX — one-shot guard private boolean executed = false; public void execute() { if (executed) throw new IllegalStateException("Command already executed"); executed = true; editor.insertText(text, position); } // ✅ MacroCommand — all-or-nothing transaction public class MacroCommand implements Command { private final List<Command> commands; private final Deque<Command> executed = new ArrayDeque<>(); public void execute() { for (Command cmd : commands) { try { cmd.execute(); executed.push(cmd); } catch (Exception e) { undo(); // rollback all executed so far throw e; } } } public void undo() { while (!executed.isEmpty()) executed.pop().undo(); // reverse order } }
execute() and undo(). Decouples the Invoker (who triggers) from the Receiver (who does the work). Enables undo/redo, queuing, logging, and transactions.execute()/undo(). ConcreteCommand holds Receiver reference + state snapshot. Invoker maintains undo/redo stacks. Client wires Command(Receiver) and passes to Invoker.Runnable, Callable). Use full Command class only when undo, queuing, or serialisation are required.