How to Implement the Command Pattern with Undo/Redo Functionality in Java

Implement the Command pattern with undo/redo by using two Deque stacks to store Runnable commands, where re-executing toggle commands reverses their effect, as demonstrated in the iluwatar/java-design-patterns repository.

The Command pattern decouples the object that invokes an operation from the object that performs it, enabling powerful features like undo and redo history. In the iluwatar/java-design-patterns repository, this pattern is implemented through a wizard casting spells on a goblin, demonstrating how to implement the Command pattern with undo/redo functionality in Java using method references and stack-based history management.

Understanding the Command Pattern Architecture

The Command pattern involves four key roles that work together to encapsulate requests as objects. The Client creates commands and configures the receiver, the Command encapsulates the request as an object, the Invoker triggers execution, and the Receiver performs the actual business logic. In the Java Design Patterns implementation, these roles map to specific classes that demonstrate clean separation of concerns while supporting undo and redo operations through toggle semantics.

Core Components in the Java Design Patterns Implementation

The Receiver (Target and Goblin)

The receiver maintains the application state that commands manipulate. In Target.java, abstract methods define the interface for state changes, while Goblin.java provides concrete implementations for changeSize() and changeVisibility(). These methods use toggle semantics, switching between enum values like Size.SMALL and Size.NORMAL, which enables undo functionality through simple re-execution rather than complex state restoration logic.

The Command (Runnable Method References)

Rather than creating dedicated command classes, the implementation leverages Java's functional interfaces to minimize boilerplate. Commands are represented as Runnable method references such as goblin::changeSize or goblin::changeVisibility. This approach reduces code complexity while maintaining the pattern's intent, as each Runnable encapsulates the invocation of a specific receiver method without requiring additional class definitions.

The Invoker (Wizard with Undo/Redo Stacks)

The Wizard class in Wizard.java serves as the invoker, managing command execution and history through two Deque<Runnable> instances. The undoStack stores executed commands, while the redoStack maintains commands that have been undone. The castSpell(Runnable) method executes commands and pushes them onto the undo stack, while undoLastSpell() and redoLastSpell() manage history navigation by transferring commands between stacks and re-executing them to toggle state.

Implementing Undo and Redo Functionality

The undo mechanism relies on the toggle semantics of receiver methods rather than separate undo logic. When undoLastSpell() is called in Wizard.java, the command is popped from undoStack, pushed onto redoStack, and executed again. Because methods like changeSize() toggle between states, re-executing the same command reverses the previous effect, effectively undoing the operation without requiring additional state storage.

Redo functionality mirrors this process exactly. The redoLastSpell() method transfers commands from redoStack back to undoStack and executes them, restoring the state to what it was before the undo. This stack-based approach provides O(1) time complexity for all operations, making it efficient for interactive applications requiring rapid history navigation.

Practical Code Examples

The following example from App.java demonstrates the complete workflow:

public static void main(String[] args) {
    var wizard = new Wizard();          // invoker
    var goblin = new Goblin();          // receiver

    // initial state
    goblin.printStatus();

    // cast two spells (commands are method references)
    wizard.castSpell(goblin::changeSize);
    goblin.printStatus();

    wizard.castSpell(goblin::changeVisibility);
    goblin.printStatus();

    // undo the last two spells
    wizard.undoLastSpell();
    goblin.printStatus();

    wizard.undoLastSpell();
    goblin.printStatus();

    // redo them
    wizard.redoLastSpell();
    goblin.printStatus();

    wizard.redoLastSpell();
    goblin.printStatus();
}

For a custom implementation, consider this light switch example using the same pattern:

public class Light {
    private boolean on = false;
    
    public void toggle() { 
        on = !on; 
        System.out.println("Light is " + (on ? "ON" : "OFF")); 
    }
}

// Usage
Wizard wizard = new Wizard();
Light lamp = new Light();

wizard.castSpell(lamp::toggle);   // Light turns ON
wizard.undoLastSpell();           // Light turns OFF (undo)
wizard.redoLastSpell();           // Light turns ON again (redo)

Key Files and Source Locations

The implementation is organized in the command module of the repository:

Summary

  • The Command pattern decouples invokers from receivers by encapsulating requests as objects, enabling undo/redo functionality through history management.
  • The iluwatar/java-design-patterns implementation uses Runnable method references as lightweight commands, eliminating boilerplate while maintaining pattern semantics.
  • Undo and redo rely on two Deque<Runnable> stacks (undoStack and redoStack) in the Wizard class, providing O(1) operation complexity.
  • Toggle semantics in receiver methods (changeSize, changeVisibility) allow the same command to serve as both action and undo operation through re-execution.
  • For complex scenarios requiring parameter storage or non-toggle logic, extend the pattern by implementing a dedicated Command interface with explicit execute() and undo() methods while retaining the stack-based history approach.

Frequently Asked Questions

How does the Command pattern enable undo functionality without separate undo methods?

The implementation leverages toggle semantics in the receiver methods. When a command like changeSize() is executed, it switches the goblin between SMALL and NORMAL states. The Wizard stores these commands in an undoStack, and when undo is requested, it re-executes the same command, which toggles the state back to its previous value. This eliminates the need for separate "unexecute" methods while maintaining clean separation between invoker and receiver.

Why does the implementation use Runnable instead of a custom Command interface?

The repository uses Runnable as the command interface to minimize boilerplate code while preserving the pattern's intent. Java 8+ method references (such as goblin::changeSize) automatically satisfy the Runnable interface, allowing commands to be created without dedicated classes. This approach is sufficient for simple toggle operations, though complex scenarios requiring parameter storage or multi-step undo logic would benefit from a custom interface with explicit execute() and undo() methods.

What is the time complexity of undo and redo operations in this implementation?

Both undo and redo operations execute in O(1) time complexity. The Wizard class uses Deque<Runnable> instances (specifically LinkedList implementations) for the undoStack and redoStack. Popping from and pushing to either end of a Deque is a constant-time operation. This efficiency makes the pattern suitable for interactive applications requiring rapid history navigation, such as text editors or drawing programs.

How would you extend this pattern to support commands with parameters or non-toggle semantics?

For commands requiring parameters or complex state restoration, replace the Runnable interface with a custom Command interface containing execute() and undo() methods. The concrete command objects would store the receiver reference, parameter values, and the previous state before execution. The undo() method would restore the saved state rather than re-executing the command. The Wizard class would remain unchanged, simply storing Command objects instead of Runnable instances in its history stacks, preserving the O(1) undo/redo performance.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →