Command Pattern

Encapsulate a request as an object, allowing parameterization of clients with different requests, queuing of requests, and logging of the requests.


Problem

You need to support undo/redo operations, queue commands for later execution, or log commands without tightly coupling the sender and receiver.

Solution

The Command pattern encapsulates requests as objects. This allows treating commands as first-class objects that can be stored, passed, and executed.


Implementation

from __future__ import annotations
from dataclasses import dataclass
from typing import List

# Receiver
class TextDocument:
    def __init__(self):
        self.text = ""

    def insert(self, s: str):
        self.text += s

    def delete_last(self, n: int):
        self.text = self.text[:-n] if n <= len(self.text) else ""

# Command interface
class Command:
    def execute(self):
        raise NotImplementedError

    def undo(self):
        raise NotImplementedError

# Concrete Commands
@dataclass
class InsertCommand(Command):
    doc: TextDocument
    s: str

    def execute(self):
        self.doc.insert(self.s)

    def undo(self):
        self.doc.delete_last(len(self.s))

@dataclass
class DeleteCommand(Command):
    doc: TextDocument
    n: int
    deleted_text: str = ""

    def execute(self):
        self.deleted_text = self.doc.text[-self.n:] if self.n <= len(self.doc.text) else self.doc.text
        self.doc.delete_last(self.n)

    def undo(self):
        self.doc.insert(self.deleted_text)

# Invoker with command history
class Editor:
    def __init__(self):
        self._undo_stack: List[Command] = []
        self._redo_stack: List[Command] = []

    def execute(self, cmd: Command):
        cmd.execute()
        self._undo_stack.append(cmd)
        self._redo_stack.clear()

    def undo(self):
        if not self._undo_stack:
            print("Nothing to undo")
            return
        cmd = self._undo_stack.pop()
        cmd.undo()
        self._redo_stack.append(cmd)

    def redo(self):
        if not self._redo_stack:
            print("Nothing to redo")
            return
        cmd = self._redo_stack.pop()
        cmd.execute()
        self._undo_stack.append(cmd)

    def show_state(self):
        print(f"Document: '{self._undo_stack[-1].doc.text if self._undo_stack else 'empty'}'")

# Demo
if __name__ == "__main__":
    doc = TextDocument()
    editor = Editor()

    editor.execute(InsertCommand(doc, "Hello "))
    print(f"After insert 'Hello ': {doc.text}")

    editor.execute(InsertCommand(doc, "World"))
    print(f"After insert 'World': {doc.text}")

    editor.undo()
    print(f"After undo: {doc.text}")

    editor.undo()
    print(f"After undo: {doc.text}")

    editor.redo()
    print(f"After redo: {doc.text}")

Key Concepts

  • Command Object: Encapsulates a request with all necessary data
  • Receiver: The object that performs the actual work (TextDocument)
  • Invoker: Stores and executes commands (Editor)
  • Undo/Redo Stack: Maintains history of executed commands

When to Use

✅ Need to implement undo/redo functionality
✅ Want to queue, log, or defer execution
✅ Need to parameterize objects with operations
✅ Building a macro or script system


Interview Q&A

Q1: How do you implement undo/redo in Command pattern?

A: Maintain two stacks: - Undo stack: Stores executed commands. Pop from stack and call undo() - Redo stack: Stores undone commands. Pop from stack and call execute(). Clear on new command.

def undo(self):
    if self._undo_stack:
        cmd = self._undo_stack.pop()
        cmd.undo()
        self._redo_stack.append(cmd)

Q2: What's the difference between Command and Strategy patterns?

A: - Command: Encapsulates requests as objects. Focuses on HOW to execute something (undo/redo, queuing). - Strategy: Encapsulates algorithms. Focuses on WHAT algorithm to use (sorting, filtering).

Commands are typically stored and executed later. Strategies are usually selected upfront and swapped.


Q3: How would you handle macro recording (sequence of commands)?

A:

class MacroCommand(Command):
    def __init__(self):
        self.commands: List[Command] = []

    def add(self, cmd: Command):
        self.commands.append(cmd)

    def execute(self):
        for cmd in self.commands:
            cmd.execute()

    def undo(self):
        for cmd in reversed(self.commands):
            cmd.undo()

Q4: Can you thread-safely execute commands?

A: Use a queue with a background worker:

import threading
import queue

class CommandQueue:
    def __init__(self):
        self.q = queue.Queue()
        self.thread = threading.Thread(target=self._worker, daemon=True)
        self.thread.start()

    def _worker(self):
        while True:
            cmd = self.q.get()
            cmd.execute()
            self.q.task_done()

    def execute(self, cmd: Command):
        self.q.put(cmd)

Q5: What happens if undo() modifies state that other commands depend on?

A: This is a critical issue. Solutions: 1. Memento Pattern: Store full state snapshot instead of incremental undo 2. Transaction Log: Maintain immutable log of all changes 3. Causal Ordering: Ensure commands execute in correct dependency order 4. Conflict Resolution: If undo conflicts with subsequent command, flag error


Trade-offs

Pros: Decouples sender/receiver, enables undo/redo, supports queuing/logging
Cons: More objects to manage, potential memory overhead for large command histories


Real-World Examples

  • Text editors (Undo/Redo in VS Code, Word)
  • Transaction systems (Database commits/rollbacks)
  • Game development (Input handling, replay system)
  • Job scheduling (Queued tasks, retry logic)