Low-level design3 min

Command Pattern

Type: Behavioral Pattern
Relevance: High. Used for undo/redo functionality, UI buttons, and job queues.

The Command pattern turns a request into a stand-alone object that contains all information about the request. This transformation lets you pass requests as a method argument, delay or queue a request's execution, and support undoable operations.

Real-Life Analogy

Think of a Restaurant Order. You (the client) give an order to the Waiter (the Invoker). The Waiter doesn't cook the food; they write the order on a piece of paper (the Command Object). They place this ticket on the kitchen counter. The Chef (the Receiver) later takes the ticket and cooks the food. Because the order is a physical piece of paper (an object), it can be queued, delayed, or even modified/undone before it's cooked.

The Problem

If you have a UI with dozens of buttons (Save, Copy, Paste, Undo), and you hardcode the business logic directly into the button click events, your UI becomes tightly coupled to your business logic. Furthermore, implementing an "Undo" button becomes nearly impossible because you haven't tracked what actions were performed.

How to Implement Command

  1. Extract the request details into a specific Command class implementing an execute() method.
  2. The UI element (Invoker) only holds a reference to a Command object and calls its execute() method.

Example in Code

typescript
// 1. The Command Interface
interface Command {
  execute(): void;
  undo(): void; // Crucial for undo/redo!
}

// 2. The Receiver (The actual business logic)
class Light {
  public turnOn(): void { console.log("Light is ON"); }
  public turnOff(): void { console.log("Light is OFF"); }
}

// 3. Concrete Commands
class TurnOnCommand implements Command {
  constructor(private light: Light) {}
  execute(): void { this.light.turnOn(); }
  undo(): void { this.light.turnOff(); }
}

class TurnOffCommand implements Command {
  constructor(private light: Light) {}
  execute(): void { this.light.turnOff(); }
  undo(): void { this.light.turnOn(); }
}

// 4. The Invoker (e.g., A Smart Home Remote Button)
class RemoteControl {
  private history: Command[] = [];

  public pressButton(cmd: Command): void {
    cmd.execute();
    this.history.push(cmd); // Save to history for undo!
  }

  public pressUndo(): void {
    const lastCmd = this.history.pop();
    if (lastCmd) {
      console.log("Undoing last action...");
      lastCmd.undo();
    }
  }
}

// Client Code
const livingRoomLight = new Light();
const turnOn = new TurnOnCommand(livingRoomLight);
const turnOff = new TurnOffCommand(livingRoomLight);

const remote = new RemoteControl();

// The remote doesn't know HOW the light works. It just executes commands.
remote.pressButton(turnOn);  // Light is ON
remote.pressButton(turnOff); // Light is OFF

// We can undo!
remote.pressUndo(); // Light is ON

Class Diagram

Why is it Important?

  • Decoupling: Decouples classes that invoke operations from classes that perform these operations.
  • Undo/Redo: It is the standard, most effective way to implement undo/redo functionality (by keeping a stack of executed Command objects and calling undo() on them).
  • Queuing: Command objects can be serialized, saved to a database, and executed later by a background worker (e.g., Job Queues).