Command Pattern in TypeScript

👀 Views 12.5K
🔄 Shares 847
⏰ Read time 7 min

The Command Pattern is a behavioral design pattern that turns requests or operations into standalone objects containing all the information needed to execute the action later. It decouples the sender (who initiates the request) from the receiver (who performs the action), enabling more flexible and maintainable code.

In TypeScript, this pattern is particularly useful for:

  • Implementing undo/redo functionality.
  • Building task queues or macros.
  • Managing UI interactions (e.g., button clicks triggering complex logic).

Command Pattern UML Diagram
Source: Wikimedia Commons

🔹 Why It Matters

  • Decoupling: Separates the requester from the executor.
  • Extensibility: Easily add new commands without modifying existing code.
  • Undo/Redo: Track and reverse actions by storing command history.
  • Transactional Behavior: Group commands into atomic operations.

🔹 Core Concepts

The Command Pattern involves four key components:

  1. Command Interface: Declares an execute() method.
  2. Concrete Command: Implements the interface and invokes the receiver’s action.
  3. Receiver: Performs the actual work.
  4. Invoker: Request the command execution.

Example Scenario: Light Control System

Imagine a smart home app controlling lights. The Invoker (remote control) triggers Commands (turn on/off), which delegate to the Receiver (light bulb).

🔹 Code Walkthrough

Let’s implement a simple light control system in TypeScript.

1. Define the Command Interface

// Command Interface
interface Command {
  execute(): void;
  undo(): void; // Optional for undo functionality
}

2. Create Concrete Commands

// Receiver (Light Bulb)
class Light {
  turnOn(): void {
    console.log("Light is ON");
  }

  turnOff(): void {
    console.log("Light is OFF");
  }
}

// Concrete Command: TurnOnLight
class TurnOnLightCommand implements Command {
  constructor(private light: Light) {}

  execute(): void {
    this.light.turnOn();
  }

  undo(): void {
    this.light.turnOff();
  }
}

// Concrete Command: TurnOffLight
class TurnOffLightCommand implements Command {
  constructor(private light: Light) {}

  execute(): void {
    this.light.turnOff();
  }

  undo(): void {
    this.light.turnOn();
  }
}

3. Implement the Invoker (Remote Control)

// Invoker (Remote Control)
class RemoteControl {
  private commandHistory: Command[] = [];

  pressButton(command: Command): void {
    command.execute();
    this.commandHistory.push(command); // Track for undo
  }

  undoLastCommand(): void {
    if (this.commandHistory.length > 0) {
      const lastCommand = this.commandHistory.pop();
      lastCommand?.undo();
    }
  }
}

4. Test the System

// Usage
const light = new Light();
const turnOnCommand = new TurnOnLightCommand(light);
const turnOffCommand = new TurnOffLightCommand(light);

const remote = new RemoteControl();
remote.pressButton(turnOnCommand); // Light is ON
remote.pressButton(turnOffCommand); // Light is OFF
remote.undoLastCommand(); // Light is ON (undo)

🔹 Common Mistakes

  1. Over-Engineering: Avoid using the Command Pattern for simple actions (e.g., direct method calls suffice).
  2. Memory Leaks: Forget to clear the command history in the Invoker.
  3. Tight Coupling: If the Command directly knows too much about the Invoker or Receiver, rethink the design.

🔹 Best Practices

  1. Keep Commands Stateless: Store only necessary data in the command object.
  2. Use Interfaces: Define clear contracts for commands and receivers.
  3. Combine with Other Patterns: Pair with Memento for robust undo/redo (Memento Pattern Guide).

🔹 Final Thoughts

The Command Pattern is a powerful tool for decoupling systems and enabling features like undo/redo and macros. In TypeScript, its type safety enhances reliability, making it ideal for complex applications.

For further reading:


This article balances theory and practice, making it accessible to beginners while providing depth for intermediate developers. Let me know if you’d like to expand on any section! 🚀


Command PatternTypeScriptDesign PatternsSoftware EngineeringUndo RedoBehavioral Patterns

Related Articles

Command Pattern in TypeScript

Command Pattern in TypeScript

Learn the Command Pattern in TypeScript with real-world examples, code walkthroughs, and best practices for decoupling actions in your applications.

Command PatternTypeScriptDesign PatternsSoftware Engineering
Abstract Factory Pattern in TypeScript

Abstract Factory Pattern in TypeScript

Master the Abstract Factory Pattern in TypeScript with clear explanations, real-world examples, and best practices. Learn how to create families of related objects efficiently.

Abstract Factory Pattern Design Patterns Object Creation TypeScript Tutorial
Observer Pattern in TypeScript

Observer Pattern in TypeScript

Master the Observer Pattern in TypeScript with clear explanations, real-world examples, and best practices for building reactive applications.

Observer PatternDesign PatternsReactive ProgrammingEvent Handling
Strategy Pattern in TypeScript

Strategy Pattern in TypeScript

Learn the Strategy Pattern in TypeScript with real-world examples, code walkthroughs, and best practices.

Strategy Pattern TypeScript Design Patterns OOP
Factory Pattern in TypeScript

Factory Pattern in TypeScript

Learn how to implement the Factory Pattern in TypeScript to create flexible and scalable applications. This guide includes real-world examples, common pitfalls, and best practices.

Factory Pattern Design Patterns Object Creation Software Engineering
Load more articles