Skip to main content

Command Pattern - TypeScript

Simple Remote Control

You have been asked to program a simple remote control. Simple remote control has only two buttons. One button to switch a device on, and another button to switch a device off.

Requirement

Customer is expecting to use this remote control to control various devices (one at at time).

For example:

  • Light - To switch On or Off a light
  • Garage Door - To Open or Close a garage door.

Customer has plans to add more devices to be controlled using the same remote control. Customer should be able to easily swap the devices assigned to the remote control.

Vendor Classes

Customer has provided code for the two devices.

class Light {
public on() {
console.log('light switched on');
}
public off() {
console.log('light is switched off');
}
}
class GarageDoor {
public open() {
console.log('Garage door opened');
}
public close() {
console.log('Garage door closed');
}
}

Approach

From the above we can see that vendor classes do not have a common interface. Light class has on and off methods where as GarageDoor has open and close methods. Same will be the case for any future devices the customer is planning to add.

One way of implementing the solution is to use if/ else to check which device is loaded on to the slot and then call the right methods.

if (slot === "Light") {
//call Off and On methods
} else if (slot === "GarageDoor") {
// call Open and Close methods
}

This approach tightly couples the client program with vendor classes. As we know, this approach will not scale and it is not easy to maintain.

In order for the remote control to be decoupled from the vendor classes, we need to introduce A Common Interface between the remote control and vendor classes.

How do we achive this ? The answer is Command pattern !

Command Pattern

Command pattern provides an interface which allows us to decouple the requester (remote control) of an action from the object that actually performs the action (Light or Ggrage Door). High level logical diagram below shows how it works.

Common Interface - Command Pattern

A command object will be introduced into the design. Command object encapsulates a request to do something on a specific object. When the customer presses a button on the remote, we ask the command object to run an interface method ex: execute(). Remote does not have any idea what the work is, it just has a command object that knows how to talk to the right object to get the work done. Hence the remote is decoupled from the actual object (ex: Light)

A sequence diagram explaing the pattem is given below.

A sequence diagram explaining command pattern

Code

Lets see how we can program the remote using Command pattern.

First we need an interface.

interface Command {
execute();
}

Then we need a command to turn on the light. Note that the command implements Command interface.

class LightONCommand implements Command {
light: Light;
constructor(light: Light) {
this.light = light;
}
execute(): void {
this.light.on();
}
}

When the customer presses a button on the remote, we call execute() method which in turn calls the on()method of light instance !

Now we need a Remote Control.

class SimpleRemoteControl {
slot: Command;
setCommand(cmd: Command) {
this.slot = cmd;
}
pressButton() {
slot.execute();
}
}

and Client Program to test the remote control.

const simpleRemote: SimpleRemoteControl = new SimpleRemoteControl(); // Invoker
const light = new Light(); // Receiver
const lightONCommand: Command = new LightONCommand(light); // Command object, with Command interface
simpleRemote.setCommand(lightONCommand); // load the command
simpleRemote.pressButton(); // runs execute() on Command object, which in turn swithes ON the light

Object Oriented Principles

We can see the following OOPs are used by Command pattern.

  • Program to interface - Client program depends only on Command interface. Any change to the Receiver objects (ex: Light) do not affect the client program.

  • Encapsulation - Command object encapsulates the Receiver object (Light instance), hence the client is unaware of the actual object which is responsible to carry out the action.

Install and Run

Link to GitHub repository:

https://github.com/smarigowda/CommandPatternTypeScript/tree/simpleRemoteLightONAndOFF

Clone my GitHub repository.

git clone git@github.com:smarigowda/CommandPatternTypeScript.git

Checkout the branch.

git checkout simpleRemoteLightONAndOFF

Install node packages.

yarn install or
npm install

Run the program

yarn test or
npm run test

Program and Output

Main program:

Main Program

Program output:

Program Output