Skip to content

Command Pattern — Encapsulate Requests as Objects

DodaTech Updated 2026-06-24 4 min read

What You'll Learn

You will learn how the Command pattern turns a request into a standalone object that can be stored, passed around, executed later, and undone. You will see how it enables features like queuing, logging, and transactional behaviour.

Why It Matters

GUI toolkits need to bind user actions (button clicks, menu selections) to application logic without hardwiring them. Each button needs a different action, and actions need undo support. Command solves this by encapsulating the action and its parameters in an object that the GUI can invoke without knowing the details. The "Undo" button doesn't know what it's undoing — it just pops the most recent command from history and calls undo().

Real-World Use

Text editors implement undo/redo by storing every user action as a Command object in a history stack. DodaTech's task scheduler queues commands representing batch jobs, allowing retry, prioritisation, and scheduled execution. Failed commands can be retried by re-queuing their command objects, and the entire schedule can be persisted by serialising the command queue.

The Pattern

Command declares the execution interface. ConcreteCommand binds an action to a receiver. Invoker triggers the command. Receiver knows how to perform the work.

from abc import ABC, abstractmethod

class Command(ABC):
    @abstractmethod
    def execute(self) -> str:
        pass

    @abstractmethod
    def undo(self) -> str:
        pass

class TextEditor:
    def __init__(self):
        self.content = ""

    def insert(self, text: str, pos: int):
        self.content = self.content[:pos] + text + self.content[pos:]

    def delete(self, start: int, length: int):
        self.content = self.content[:start] + self.content[start + length:]

class InsertCommand(Command):
    def __init__(self, editor: TextEditor, text: str, pos: int):
        self._editor = editor
        self._text = text
        self._pos = pos

    def execute(self) -> str:
        self._editor.insert(self._text, self._pos)
        return f"Inserted '{self._text}' at {self._pos}"

    def undo(self) -> str:
        self._editor.delete(self._pos, len(self._text))
        return f"Undone insert of '{self._text}'"

class DeleteCommand(Command):
    def __init__(self, editor: TextEditor, start: int, length: int):
        self._editor = editor
        self._start = start
        self._length = length
        self._deleted = ""

    def execute(self) -> str:
        self._deleted = self._editor.content[self._start:self._start + self._length]
        self._editor.delete(self._start, self._length)
        return f"Deleted '{self._deleted}'"

    def undo(self) -> str:
        self._editor.insert(self._deleted, self._start)
        return f"Undone delete of '{self._deleted}'"

class CommandHistory:
    def __init__(self):
        self._stack = []

    def execute(self, cmd: Command):
        result = cmd.execute()
        self._stack.append(cmd)
        return result

    def undo_last(self):
        if self._stack:
            cmd = self._stack.pop()
            return cmd.undo()
        return "Nothing to undo"
editor = TextEditor()
history = CommandHistory()
print(history.execute(InsertCommand(editor, "Hello", 0)))
print(history.execute(InsertCommand(editor, " World", 5)))
print(f"Content: '{editor.content}'")
print(history.execute(DeleteCommand(editor, 5, 6)))
print(f"Content: '{editor.content}'")
print(history.undo_last())
print(f"Content: '{editor.content}'")
Inserted 'Hello' at 0
Inserted ' World' at 5
Content: 'Hello World'
Deleted ' World'
Content: 'Hello'
Undone delete of ' World'
Content: 'Hello World'

Structure

classDiagram
    class Command {
        <>
        +execute()
        +undo()
    }
    class ConcreteCommand {
        -receiver: Receiver
        +execute()
        +undo()
    }
    class Invoker {
        -command: Command
        +executeCommand()
    }
    class Receiver {
        +action()
    }
    class Client {
    }
    ConcreteCommand ..|> Command
    ConcreteCommand --> Receiver : uses
    Invoker --> Command : triggers
    Client --> ConcreteCommand : creates

Real-World Usage

  • Java java.lang.Runnable — encapsulates a task as an object for thread execution.
  • Undo/redo in Photoshop, VS Code, and Git — every action is a command recorded in history.
  • CQRS Pattern — command objects encapsulate write operations separate from read queries.
  • Transactional outbox — database changes are queued as commands to ensure reliable message publishing.
  • Chain of Responsibility passes requests along; Command encapsulates them as objects.
  • Memento captures state; Command captures operations.
  • Strategy encapsulates algorithms; Command encapsulates requests.
  • Observer notifies changes; Command triggers changes.

Pros and Cons

Pros Cons
Decouples invoker from receiver Increases number of classes per operation
Supports undo/redo and logging Command objects may accumulate memory for undo history
Enables queuing and delayed execution Simple operations become overly ceremonial
New commands can be added without changing existing code Receiver interface changes force updates to all commands

The undo/redo example demonstrates the pattern's power. Each command stores enough information to reverse itself. InsertCommand remembers the text and position; DeleteCommand saves the deleted text during execute() so it can be restored during undo(). The CommandHistory invoker simply stacks and executes commands — it never needs to understand what each command does.

Practice Questions

  1. Implement a macro command that executes a sequence of sub-commands in order, and supports undo by reversing the sub-command order.
  2. How would you persist a command queue to disk to survive application restarts?
  3. Compare Command with callbacks and lambdas — when does the pattern become worth the overhead?
  4. Implement a command retry mechanism with exponential backoff for transient failures.

Challenge

Implement a RedoCommand that reverses the last undo. This requires a second stack of undone commands that can be re-applied.

Real-World Task

Identify a sequence of operations in your application that users might want to undo. Encapsulate each operation as a Command and implement an undo/redo history using DodaTech's action replay tool to verify correctness.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro