Skip to content
SP StackPractices
intermediate

Command Pattern

Encapsulate a request as an object, letting you parameterize clients with queues, logs, and undoable operations. A behavioral design pattern.

Topics: design

Command Pattern

Overview

The Command Pattern is a behavioral design pattern that turns a request into a stand-alone object containing all information about the request. This lets you parameterize methods with different requests, delay or queue execution, and support undoable operations.

It is the basis for undo/redo systems, job queues, macro recording, and transactional operations.

When to Use

Use the Command Pattern when:

  • You need to parameterize objects with operations to execute
  • You want to queue, schedule, or execute operations remotely
  • You need undo/redo functionality
  • You want to log changes for replay or audit purposes
  • You need transactional behavior (execute all or roll back)

Solution

Python

from abc import ABC, abstractmethod

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

    @abstractmethod
    def undo(self):
        pass

class Light:
    def __init__(self):
        self.is_on = False

    def turn_on(self):
        self.is_on = True
        print("Light is on")

    def turn_off(self):
        self.is_on = False
        print("Light is off")

class TurnOnCommand(Command):
    def __init__(self, light: Light):
        self.light = light

    def execute(self):
        self.light.turn_on()

    def undo(self):
        self.light.turn_off()

# Usage
light = Light()
cmd = TurnOnCommand(light)
cmd.execute()  # Light is on
cmd.undo()     # Light is off

JavaScript

class Light {
  constructor() {
    this.isOn = false;
  }
  turnOn() {
    this.isOn = true;
    console.log("Light is on");
  }
  turnOff() {
    this.isOn = false;
    console.log("Light is off");
  }
}

class TurnOnCommand {
  constructor(light) {
    this.light = light;
  }
  execute() {
    this.light.turnOn();
  }
  undo() {
    this.light.turnOff();
  }
}

// Usage
const light = new Light();
const cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo();    // Light is off

Java

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

class Light {
    boolean isOn = false;
    void turnOn() { isOn = true; System.out.println("Light is on"); }
    void turnOff() { isOn = false; System.out.println("Light is off"); }
}

class TurnOnCommand implements Command {
    private final Light light;
    TurnOnCommand(Light light) { this.light = light; }
    public void execute() { light.turnOn(); }
    public void undo() { light.turnOff(); }
}

// Usage
Light light = new Light();
Command cmd = new TurnOnCommand(light);
cmd.execute(); // Light is on
cmd.undo();    // Light is off

Explanation

The Command Pattern separates action invocation from execution:

  • Command Interface: Declares execute() and optionally undo()
  • Concrete Command (TurnOnCommand): Binds a receiver (Light) to an action (turnOn)
  • Receiver (Light): The object that performs the actual work
  • Invoker: Calls execute() on commands (e.g., a button, scheduler, or remote control)

By encapsulating requests as objects, you gain the ability to queue, log, and reverse operations.

Variants

VariantUse CaseTrade-off
Simple CommandDirect action with no undoEasy to implement, limited flexibility
Undoable CommandOperations that can be reversedRequires maintaining state for reversal
Macro CommandComposite of multiple commandsPowerful, but harder to undo atomically

Best Practices

  • Implement undo() for every command if your system supports undo
  • Keep commands stateless when possible: Store receiver state, not command state
  • Use a command history (stack) to support multi-level undo/redo
  • Document side effects: Commands that affect external systems are harder to undo
  • Consider immutability: Once configured, a command should not change its target

Common Mistakes

  • Forgetting undo state: Commands that cannot be reversed break the undo stack
  • Tight coupling: Commands that depend on global state instead of a specific receiver
  • Over-engineering: Using Command for trivial, one-off operations that never need queuing or undo
  • Synchronous assumptions: Not considering that commands may be executed asynchronously
  • Missing idempotency: Running the same command twice produces different results

Frequently Asked Questions

Q: What is the difference between Command and Strategy? A: Strategy encapsulates interchangeable algorithms. Command encapsulates a request to perform an action, often with support for undo, queuing, and logging.

Q: Can Command be used without undo? A: Yes. The undo capability is optional. Many systems use Command solely for queuing and decoupling invokers from receivers.

Q: How do I implement multi-level undo? A: Maintain a stack of executed commands. Undo pops the stack and calls undo(). Redo pushes the command back and calls execute().