SSourav Saha
HomeExperienceSoftware DesignSystem DesignLearningBooksToolsContact
SSourav Saha

Building scalable backend systems, distributed infrastructure, and cloud-native applications.

Navigation

  • Home
  • Experience
  • Software Design
  • System Design
  • Learning

More

  • Books
  • Tools
  • Contact

Connect

  • LinkedIn
  • Email

© 2026 Sourav Saha. All rights reserved.

Built with using Next.js

Software DesignCommand Pattern
Software DesignBehavioral Patterns

Command Pattern

Encapsulate a request as an object — enabling undo, queuing, logging, and macro recording.

December 18, 20245 min read
design-patternsjavacommandbehavioral

One-line Definition

Encapsulates an operation as an object, allowing requests to be queued, logged, or undone transparently.


Problem

Suppose you are building a text editor with UI buttons for Copy, Paste, and Undo. If the "Copy" button directly calls Editor.copy(), the button is tightly coupled to the editor. If you later want to implement keyboard shortcuts that do the exact same thing, you have to duplicate the logic. Furthermore, how do you implement "Undo" if actions are just immediate method calls? The Command Pattern solves this by turning every action into a standalone object that can be executed, stored in a history stack, or reversed.


Real-world Analogy

At a restaurant, you don't walk into the kitchen and cook your food. You tell the waiter "I'd like a steak." The waiter writes your order (command) on a slip. The slip goes to the kitchen (receiver). The waiter (invoker) doesn't know how to cook — they just pass the command. If you change your mind, the waiter can cancel the slip.


Structure

  • Client
    • Creates Command objects and sets their receivers.
  • Invoker
    • Holds a reference to a Command and triggers its execution (e.g., a GUI Button or a History Stack).
  • Command
    • Interface declaring an execute() method (and optionally undo()).
  • ConcreteCommand
    • Binds the Receiver to an action. Implements execute() by calling the Receiver's methods.
  • Receiver
    • The object that actually performs the business logic (e.g., the TextEditor).

Diagram

classDiagram
    class Client {
    }
    class Invoker {
        -Command command
        +executeCommand()
    }
    class Command {
        <<interface>>
        +execute()
        +undo()
    }
    class ConcreteCommand {
        -Receiver receiver
        +execute()
        +undo()
    }
    class Receiver {
        +action()
    }
    
    Client --> Invoker
    Client --> ConcreteCommand
    Invoker o-- Command : stores
    ConcreteCommand ..|> Command
    ConcreteCommand --> Receiver : calls

Code Walkthrough

Notice how the EditorInvoker maintains a history stack of Command objects. It doesn't know what the commands actually do; it just calls execute() and undo().

interface Command {
    void execute();
    void undo();
}

class TextEditor {
    private StringBuilder text = new StringBuilder();

    public void write(String content) {
        text.append(content);
        System.out.println("Text: " + text);
    }

    public void eraseLast(int count) {
        int start = Math.max(0, text.length() - count);
        String erased = text.substring(start);
        text.delete(start, text.length());
        System.out.println("Erased '" + erased + "'. Text: " + text);
    }
}

class WriteCommand implements Command {
    private final TextEditor editor;
    private final String content;

    public WriteCommand(TextEditor editor, String content) {
        this.editor = editor;
        this.content = content;
    }

    @Override
    public void execute() { editor.write(content); }

    @Override
    public void undo() { editor.eraseLast(content.length()); }
}

class EditorInvoker {
    private final Stack<Command> history = new Stack<>();

    public void executeCommand(Command command) {
        command.execute();
        history.push(command);
    }

    public void undo() {
        if (!history.isEmpty()) {
            Command command = history.pop();
            command.undo();
        } else {
            System.out.println("Nothing to undo");
        }
    }
}

class Main {
    public static void main(String[] args) {
        TextEditor editor = new TextEditor();
        EditorInvoker invoker = new EditorInvoker();

        invoker.executeCommand(new WriteCommand(editor, "Hello "));
        invoker.executeCommand(new WriteCommand(editor, "World"));
        invoker.executeCommand(new WriteCommand(editor, "!"));

        System.out.println("--- Undoing ---");
        invoker.undo(); // Removes "!"
        invoker.undo(); // Removes "World"
    }
}

Bad vs Good

Bad Approach

Problems

  • The UI component (Button) is tightly coupled to the business logic (Editor).
  • No way to implement an "Undo" feature because method calls are immediately forgotten.
class Button {
    private final TextEditor editor;

    public Button(TextEditor editor) { this.editor = editor; }

    public void onClick(String text) {
        editor.write(text);  // Direct call — no undo, no history
    }
}

Better Approach

Improvements

  • The UI (Button/Invoker) only knows about the Command interface.
  • You gain full undo/redo history completely for free.
class Button {
    private final Command command;

    public Button(Command command) { this.command = command; }

    public void onClick() {
        command.execute();  // Decoupled — undo, history, queue all possible
    }
}

Pros vs Cons

ProsCons
Fully decouples the UI (invokers) from the business logic (receivers)Requires creating a new class for every distinct action
Enables powerful features like Undo, Redo, and Macro recordingCan significantly bloat the codebase for simple applications
Commands can be serialized to disk or placed in a queueManaging complex state for undo() operations is difficult
Adheres strictly to the Single Responsibility Principle
Easy to add new commands without modifying existing code

When to Use

  • You need to support undo/redo operations.
  • You need to parameterize objects with actions (e.g., assigning different actions to UI buttons).
  • You want to queue tasks, schedule their execution, or execute them remotely.
  • You want to implement macros (a sequence of commands executed together).

When Not to Use

  • The application is incredibly simple, and commands are just direct wrappers around trivial method calls.
  • You have no need for undo functionality, queuing, or decoupling UI from business logic.

Real-world Examples

  • java.lang.Runnable (The classic Command interface for threading)
  • javax.swing.Action (Swing's implementation of the Command pattern for UI actions)
  • Thread Pools and Task Executors (which consume Queues of Command objects).

Key Takeaway

The Command Pattern treats method calls as first-class objects. By wrapping a request in an object, you decouple the sender from the receiver and gain the ability to store, queue, and reverse operations. Use it for complex UIs or transactional systems, but stick to direct method calls if you don't need history or queuing.

PreviousComposite PatternNextStrategy Pattern