TypeScript Command Pattern Example

1. Definition

The Command design pattern is a behavioral design pattern that turns a request into a stand-alone object that contains information about the request, such as the method name, the object that invokes the command, and the values for the method parameters. This decoupling allows parameterization of objects with different requests and enables saving, queueing, and executing requests at different times.

2. Problem Statement

Imagine you're building a UI toolkit, and you want to enable undo, redo, or macro functionalities. Directly hardcoding operations into buttons or menu items can become unmanageable, especially as the system grows.

3. Solution

Instead of hardcoding operations, encapsulate them in command objects. This allows you to decouple classes that invoke operations from classes that perform the operation. It provides greater flexibility in object construction, invocation, and operations.

4. Real-World Use Cases

1. Implementing "undo" functionality in text editors.

2. Macro recording in software applications.

3. Operations queuing in thread pools.

5. Implementation Steps

1. Define a command interface with an execute method.

2. Create one or more derived classes that encapsulate a request by binding it to the set of arguments.

3. Instantiate a command object for each request.

4. The invoker class is responsible for the execution of the command.

6. Implementation in TypeScript

// Step 1: Command interface
interface Command {
    execute(): void;
}
// Step 2: Concrete commands that implement the Command interface
class LightOnCommand implements Command {
    constructor(private light: Light) {}
    execute() {
        this.light.turnOn();
    }
}
class LightOffCommand implements Command {
    constructor(private light: Light) {}
    execute() {
        this.light.turnOff();
    }
}
// Receiver class
class Light {
    turnOn() {
        console.log('Light is ON');
    }
    turnOff() {
        console.log('Light is OFF');
    }
}
// Invoker
class RemoteControl {
    private command: Command;
    constructor(command: Command) {
        this.command = command;
    }
    pressButton() {
        this.command.execute();
    }
}
// Client code
const light = new Light();
const lightOn = new LightOnCommand(light);
const lightOff = new LightOffCommand(light);
const remote = new RemoteControl(lightOn); // Passing a LightOn command
remote.pressButton();
const remote2 = new RemoteControl(lightOff); // Passing a LightOff command
remote2.pressButton();

Output:

Light is ON
Light is OFF

Explanation:

In the TypeScript example, the Command pattern encapsulates the Light's operations (turnOn and turnOff) into LightOnCommand and LightOffCommand. The RemoteControl class, which acts as the invoker, can then execute these commands without needing to know the specifics of the operation.

7. When to use?

Use the Command pattern when:

1. You need to decouple the object that invokes the operation from the one that knows how to execute it.

2. You want to support undoable operations.

3. You want to support logging changes.

4. You need a callback function with a clear, extensible interface.


Comments