Undoable Command Design Pattern
The Undoable Command design pattern is a behavioral pattern that extends the Command pattern to allow operations to be reversed. This pattern is useful when you want to implement features like “undo” or “redo” in applications. Here’s a breakdown of how the pattern works and how it can be implemented.
Key Components
- Command Interface: This defines the contract for the command, typically with methods like
execute()
andunexecute()
(to undo the command). - Concrete Command: A specific implementation of the
Command
interface, where each command encapsulates a specific action and how to reverse it. It maintains the state needed to undo itself. - Invoker: This object triggers the execution of commands. It may also maintain a history stack of executed commands for easy access when undoing/redoing actions.
- Receiver: The object that actually performs the work. The command object will delegate work to this receiver.
- History Stack: A stack of executed commands maintained to allow the application to step back through previously executed commands and undo them.
Example in Java
Here’s an example of the Undoable Command pattern in Java.
Step 1: Define the Command Interface
interface Command {
void execute();
void unexecute();
}
Step 2: Create the Receiver
The receiver could be anything, but let’s say we have a TextEditor
that allows adding or removing text.
class TextEditor {
private StringBuilder text = new StringBuilder();
public void addText(String newText) {
text.append(newText);
}
public void removeText(int length) {
text.delete(text.length() - length, text.length());
}
public String getText() {
return text.toString();
}
}
Step 3: Implement Concrete Commands
Let’s create two commands: one to add text and one to remove it.
class AddTextCommand implements Command {
private TextEditor editor;
private String text;
public AddTextCommand(TextEditor editor, String text) {
this.editor = editor;
this.text = text;
}
@Override
public void execute() {
editor.addText(text);
}
@Override
public void unexecute() {
editor.removeText(text.length());
}
}
Step 4: Implement the Invoker
The Invoker
will keep track of command history and manage the undo operations.
import java.util.Stack;
class CommandInvoker {
private Stack<Command> commandHistory = new Stack<>();
public void executeCommand(Command command) {
command.execute();
commandHistory.push(command);
}
public void undo() {
if (!commandHistory.isEmpty()) {
Command command = commandHistory.pop();
command.unexecute();
}
}
}
Step 5: Using the Pattern
Here’s how we can use the Undoable Command
pattern.
public class Main {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
CommandInvoker invoker = new CommandInvoker();
Command addHello = new AddTextCommand(editor, "Hello ");
Command addWorld = new AddTextCommand(editor, "World!");
invoker.executeCommand(addHello);
invoker.executeCommand(addWorld);
System.out.println("After additions: " + editor.getText());
invoker.undo();
System.out.println("After undo: " + editor.getText());
invoker.undo();
System.out.println("After second undo: " + editor.getText());
}
}
Advantages
- Separation of Concerns: Each command is encapsulated as an object, making it easy to add, remove, or modify commands without changing other code.
- Undo/Redo Support: The pattern inherently provides a way to implement undo/redo functionality.
- Extensibility: New commands can be added without altering existing code.
Use Cases
- Text editors (undo/redo actions)
- Drawing applications (undo/redo shapes and transformations)
- Transaction management in databases (rollbacks)
The Undoable Command pattern is especially useful in applications where user actions need to be reversible, offering a flexible and powerful way to handle complex sequences of user interactions.