Imagine you are building a text editor. You need to implement operations like “copy,” “paste,” and “delete.” You also need to support undoing these operations. How do you design this in a clean, extensible way?

The Command pattern provides a solution. It encapsulates a request (an action) as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

The Core Idea: Actions as Objects

Instead of calling a method directly on a receiver object, you create a “Command” object that holds all the necessary information to perform the action. This includes:

  • The action to be performed (e.g., a method reference).
  • The object that will perform the action (the “receiver”).
  • Any parameters needed for the action.

This command object has a simple, common interface, typically a single execute() method.

Example: A Simple Text Editor

Let’s model a basic text editor that can add and delete text.

The Receiver

This is the object that does the actual work.

// The Receiver: Performs the actual operations
class TextEditor {
  private content: string = "";

  addText(text: string): void {
    this.content += text;
    console.log(`Current content: "${this.content}"`);
  }

  deleteText(chars: number): void {
    this.content = this.content.slice(0, -chars);
    console.log(`Current content: "${this.content}"`);
  }
}

The Command Interface

All concrete commands will implement this interface. It often includes an undo() method for undoable operations.

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

Concrete Commands

These classes bind a receiver with a specific action.

class AddTextCommand implements Command {
  constructor(
    private editor: TextEditor,
    private textToAdd: string
  ) {}

  execute(): void {
    this.editor.addText(this.textToAdd);
  }

  undo(): void {
    this.editor.deleteText(this.textToAdd.length);
  }
}

class DeleteTextCommand implements Command {
  private deletedText: string = ""; // We need to store this for undo

  constructor(
    private editor: TextEditor,
    private charsToDelete: number
  ) {}

  execute(): void {
    // Before deleting, we need to know what text was deleted
    // In a real app, you'd get this from the editor
    this.deletedText = " ... some text ... "; // Simplified for example
    this.editor.deleteText(this.charsToDelete);
  }
  
  undo(): void {
    this.editor.addText(this.deletedText); // Not perfectly accurate, but shows the concept
  }
}

The Invoker

The invoker is responsible for executing commands and, crucially, can keep a history of them to manage undo/redo.

class EditorInvoker {
  private history: Command[] = [];

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

  undoLastCommand(): void {
    const lastCommand = this.history.pop();
    if (lastCommand) {
      lastCommand.undo();
    }
  }
}

// Putting it all together
const editor = new TextEditor();
const invoker = new EditorInvoker();

const addHello = new AddTextCommand(editor, "Hello ");
const addWorld = new AddTextCommand(editor, "World!");

invoker.executeCommand(addHello); // "Hello "
invoker.executeCommand(addWorld); // "Hello World!"

invoker.undoLastCommand(); // "Hello "
invoker.undoLastCommand(); // ""

Python Example: A Smart Home Remote Control

In Python, we can use callable objects or classes to implement the same pattern. Let’s create a remote control for smart home devices.

from abc import ABC, abstractmethod

# The Receiver classes
class Light:
    def turn_on(self):
        print("Light is ON")

    def turn_off(self):
        print("Light is OFF")

class Fan:
    def start_rotating(self):
        print("Fan is ON")
    
    def stop_rotating(self):
        print("Fan is OFF")

# The Command interface and Concrete Commands
class Command(ABC):
    @abstractmethod
    def execute(self):
        pass
    
    @abstractmethod
    def undo(self):
        pass

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

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

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

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

    def execute(self):
        self._light.turn_off()

    def undo(self):
        self._light.turn_on()
        
# A command that does nothing (Null Object Pattern)
class NoCommand(Command):
    def execute(self):
        pass
    def undo(self):
        pass

# The Invoker: Our remote control
class RemoteControl:
    def __init__(self):
        self._on_buttons: list[Command] = [NoCommand()] * 3
        self._off_buttons: list[Command] = [NoCommand()] * 3
        self._undo_command: Command = NoCommand()

    def set_command(self, slot: int, on_command: Command, off_command: Command):
        self._on_buttons[slot] = on_command
        self._off_buttons[slot] = off_command

    def press_on_button(self, slot: int):
        command = self._on_buttons[slot]
        command.execute()
        self._undo_command = command

    def press_off_button(self, slot: int):
        command = self._off_buttons[slot]
        command.execute()
        self._undo_command = command
        
    def press_undo_button(self):
        print("--- UNDO ---")
        self._undo_command.undo()
        self._undo_command = NoCommand()

# Client Code
light = Light()
light_on = TurnOnLightCommand(light)
light_off = TurnOffLightCommand(light)

remote = RemoteControl()
remote.set_command(0, light_on, light_off)

remote.press_on_button(0)   # Light is ON
remote.press_off_button(0)  # Light is OFF
remote.press_undo_button()  # --- UNDO --- \n Light is ON

Benefits of the Command Pattern

  1. Decouples the Invoker from the Receiver: The object that initiates an action (RemoteControl) knows nothing about the object that performs it (Light). It only knows it has a Command object with an execute() method.
  2. Enables Undo/Redo: By storing a history of command objects, you can easily implement multi-level undo and redo functionality.
  3. Supports Queuing and Asynchronous Operations: Command objects can be stored in a queue and processed later, perhaps by a worker thread or a different process.
  4. Creates Composable Actions: You can create a MacroCommand that holds a list of other commands and executes them in sequence. This allows you to build complex operations from simpler ones.
  5. Improves Testability: You can test the invoker and the receiver in isolation by providing mock command objects.

The Command pattern is a versatile behavioral pattern that adds a layer of abstraction between the “what” and the “how” of an operation. It’s a cornerstone of interactive applications, background job processors, and any system that needs to manage actions as first-class objects.