TypeScript Command Design Pattern

Ibrahim sengun
3 min readJan 20, 2024

--

What is command design pattern?

The command design pattern is a behavioral design pattern. It provides the ability to turn complicated objects or requests into standalone objects that contain all the related information. With this transformation, a request’s execution can be altered or delayed based on needs.

There are several terminologies in the command design pattern. These are:

  • Invoker: It handles the initiation of the requests and stores references for command objects.
  • Command: It is the interface that defines the properties of the command.
  • Concrete Command: It’s the class that implements the command interface.
  • Receiver: It contains the business logic for commands. It’s the place where command objects pass the requests.
  • Client: This refers to the application or function that communicates with the system

When should the command design pattern be used?

The common design pattern that can be used when there is a need to parameterize the objects with operations. With this pattern, you can pass commands as method arguments or store them inside other objects.

It can also be employed when there is a need to queue operations or schedule their executions. Due to the nature of command objects being standalone, these objects can be serialized, allowing them to be stored at will. This provides the ability to queue or delay the execution of objects.

Another important area of usage for command objects is that they can be reversed. This means operations can be undone and redone, or even logged for future reference. However, if command objects have dependencies on private fields, reversing or storing the actual state of the command objects can be challenging.

How to implement command design pattern in TypeScript

Let’s apply the Command design pattern to TypeScript. First, let’s imagine a scenario where we are dealing with electronic applications in our home. Initially, we designed a simple system that gave us the ability to control one application. However, after some time, our needs increased, and we had multiple electronic applications, each with specific control properties. So, we expanded our system on a linear basis — more electronic applications resulted in more subclasses.

Unfortunately, this approach didn’t work well for us in the long run. Even though we could handle the controls of the electronic applications, our system became bloated with subclasses. This became especially problematic when we needed a specific order or sequence to control all electronic applications at once. After realizing these issues, we decided to employ the Command design pattern. This allowed us to reduce coupling and build a system where we could gain control over the order sequence of the commands.

Command design pattern diagram

Command design pattern diagram

Command design pattern code

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

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

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

// Concrete Command
class TurnOnCommand implements Command {
private light: Light;

constructor(light: Light) {
this.light = light;
}

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

// Concrete Command
class TurnOffCommand implements Command {
private light: Light;

constructor(light: Light) {
this.light = light;
}

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

// Invoker
class RemoteControl {
private command?: Command;

setCommand(command: Command): void {
this.command = command;
}

pressButton(): void {
if (this.command) {
this.command.execute();
} else {
console.log("No command set");
}
}
}

// Client
const livingRoomLight = new Light();
const remoteControl = new RemoteControl();

// Output: Light is ON
const turnOnCommand = new TurnOnCommand(livingRoomLight);
remoteControl.setCommand(turnOnCommand);
remoteControl.pressButton();

// Output: Light is OFF
const turnOffCommand = new TurnOffCommand(livingRoomLight);
remoteControl.setCommand(turnOffCommand);
remoteControl.pressButton();

--

--