Command Pattern: Encapsulating Actions as Objects

Situation: You’re Building a UI with Buttons

You have a GUI or CLI where a user can:

  • Click “Save”
  • Click “Print”
  • Click “Undo”
  • Click “Redo”

You write something like:

if (button.equals("save")) saveDog();
else if (button.equals("print")) printDoc();
else if (button.equals("undo")) undo();

Then Requirements Start Stacking

  • Add keyboard shortcuts for each action
  • Add macros (run a sequence of commands)
  • Add undo history
  • Add dynamic remapping of buttons
  • Each action might need different parameters

Now your code is tangled. Logic is everywhere. You can’t:

  • Queue actions
  • Log what was done
  • Revert past actions
  • Run same logic later with same behavior

You’re writing “action triggers” and “execution logic” in the same place - it’s a giant ball of mud.

What’s the Problem?

  • You’re mixing what to do with how to trigger it
  • No clean way to queue, undo, or reuse actions
  • Impossible to treat actions like objects
  • No audit trail, no flexibility

How Command Pattern Saves You

Command pattern says:

“Wrap every action inside a Command object - now it can be passed around, stored, and executed when needed.”

Instead of directly calling methods, you create command classes that encapsulate an action and its parameters. Each command implements a common interface with methods like execute() and undo().

Interface

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

Concrete commands

// Concrete commands
class SaveCommand implements Command {
  private document: Document;
  
  constructor(document: Document) {
    this.document = document;
  }
  
  execute() {
    this.document.save();
  }
  
  undo() {
    // Restore previous state
  }
}

Invoker class (e.g., a button)

// Command invoker
class Button {
  private command: Command;
  
  setCommand(command: Command) {
    this.command = command;
  }
  
  click() {
    this.command.execute();
  }
}

Usage


Button saveBtn = new Button(new SaveCommand());
Button printBtn = new Button(new PrintCommand());

saveBtn.click();  // Output: Saving the document...
printBtn.click(); // Output: Printing the document...

Benefits of the Command Pattern

  • Separation of concerns: The invoker is decoupled from the receiver
  • Extensibility: Add new commands without changing existing code
  • History: Store command objects for audit logs or undo functionality
  • Queuing: Commands can be scheduled and executed later
  • Composability: Create macro commands that execute multiple commands
  • Transactional behavior: Roll back a sequence of commands if one fails

Real-World Use Cases

  • GUI actions (buttons, menu items, keyboard shortcuts)
  • Transactional systems where operations need to be rolled back
  • Remote procedure calls where commands are serialized and sent over a network
  • Task schedulers and job queues
  • Multi-level undo/redo functionality
  • Gaming input systems

When to Use It

Use the Command Pattern when you need to:

  1. Parameterize objects with operations
  2. Queue, schedule, or execute operations remotely
  3. Support undoable operations
  4. Structure a system around high-level operations
  5. Implement callback functionality in a type-safe way

By encapsulating actions as objects, the Command Pattern brings flexibility and structure to your code, making it easier to extend and maintain as requirements grow.