Fundamentals 13 min read

Command Pattern: Turning Operations into Objects for Undo, Redo, and Queuing

The article explains how the Command pattern encapsulates a request as an object, enabling storage, queuing, undo/redo, and decoupling between request senders and executors, with concrete Java examples and guidance on when the pattern is appropriate.

Dabaoshi
Dabaoshi
Dabaoshi
Command Pattern: Turning Operations into Objects for Undo, Redo, and Queuing

1. Problem with immediate method calls

In an order‑admin service, operations such as order.setAddress("Beijing Chaoyang"), order.addRemark("Ship ASAP"), or order.setCount(3) are invoked directly. The call is fleeting, the caller ( OrderAdminService) is tightly coupled to the receiver ( order), and there is no way to log, queue, check permissions, or undo the operation.

2. Command pattern: encapsulating an operation as an object

The pattern introduces a command object that represents “what to do”. Once an operation is an object, it can be stored, passed around, queued, logged, or undone.

3. Defining the command interface

public interface Command {
    void execute(); // perform the operation
    void undo();    // reverse the operation
}

4. Concrete command example

public class ChangeCountCommand implements Command {
    private final Order order;          // receiver
    private final int newCount;
    private int oldCount;                // remembers previous state

    public ChangeCountCommand(Order order, int newCount) {
        this.order = order;
        this.newCount = newCount;
    }

    @Override
    public void execute() {
        this.oldCount = order.getCount(); // capture state before change
        order.setCount(newCount);
    }

    @Override
    public void undo() {
        order.setCount(oldCount); // restore previous state
    }
}
// ChangeAddressCommand, AddRemarkCommand follow the same pattern

5. Invoker with a command‑history stack

public class OrderCommandInvoker {
    private final Deque<Command> history = new ArrayDeque<>(); // executed commands

    public void execute(Command command) {
        command.execute();
        history.push(command); // record for possible undo
    }

    public void undo() {
        if (!history.isEmpty()) {
            Command last = history.pop();
            last.undo();
        }
    }
}

6. Using the invoker

OrderCommandInvoker invoker = new OrderCommandInvoker();
invoker.execute(new ChangeAddressCommand(order, "Beijing Chaoyang")); // change address
invoker.execute(new ChangeCountCommand(order, 3));                // change count
invoker.undo(); // undo count change
invoker.undo(); // undo address change

7. Comparison with direct calls

Before, each operation was a one‑shot method call. After applying the pattern, every operation becomes a storable command object; the invoker no longer calls order directly but triggers commands, achieving decoupling and making undo a simple “pop‑stack + undo() ” operation.

8. Visual overview

Command pattern structure
Command pattern structure

9. Roles in the pattern

Command : declares execute() and undo().

ConcreteCommand : e.g., ChangeCountCommand, encapsulates the receiver, parameters, and undo data.

Receiver : the actual business object, such as Order.

Invoker : OrderCommandInvoker, triggers commands and manages the history stack.

10. Undo and redo mechanics

Undo : each executed command is pushed onto an “executed stack”. Undo pops the latest command and calls its undo(), which restores the previously saved state.

Redo : a separate “undone stack” stores commands that have been undone. Redo pops from this stack, calls execute() again, and pushes back onto the executed stack.

11. Real‑world manifestations

Runnable

/ thread‑pool tasks are pure command objects; the executor acts as the invoker.

Message or task queues serialize command objects for distributed processing.

Database transactions keep redo/undo logs, mirroring the command’s undo concept.

GUI menu items and editor actions (Ctrl+Z / Ctrl+Y) are classic command‑pattern examples.

Spring’s JdbcTemplate callbacks ( XxxCallback) follow the same idea.

12. When to apply the Command pattern

You need to store, queue, delay, or execute operations asynchronously.

You require undo/redo capabilities.

You want to decouple the request sender from the executor.

You wish to add cross‑cutting concerns (logging, transactions, permissions) uniformly in the invoker.

13. When not to use it

The operation is simple, one‑off, and does not need undo or queuing – direct calls are clearer.

There is no need to treat operations as objects – adding command classes would be over‑engineering.

14. Practical tip for modern Java

If the command logic is trivial, a lambda or method reference (e.g., Runnable or Consumer) can be used instead of a full command class; write concrete command classes only when state or custom undo logic is required.

15. Summary

The Command pattern turns a request into a first‑class object, enabling storage, queuing, logging, undo, and redo while decoupling the initiator from the executor. Its core is the “invoker → command object → receiver” chain and a command‑history stack. Use it when those capabilities are required; otherwise, prefer lightweight functional interfaces.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Design PatternsJavaDecouplingCommand PatternUndoRedoInvoker
Dabaoshi
Written by

Dabaoshi

Practical utilities

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.