Let's start with the terse description given in the book: the Command Pattern…

… encapsulates a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

This basically means separating the action to be performed, from the thing that is performing the action.

The first example given in the book is a useful one: buttons and menu items in a user interface. Let's see a quick example of a piece of code not implementing the command pattern. This is a button that logs out "Hello World!". Not the most useful button, but probably the most succinct example:

class Button {
    click(): void {
        console.log("Hello World!");
    }
}

Notice how the action we want to perform, logging "Hello World", is tied to the button, meaning it would be difficult (ha, ha) to implement this functionality elsewhere, test this functionality, or to swap this functionality at a later time.

To implement this button in the style of the command pattern, we'd rewrite it to be similar to the following:

interface Command {
    execute(): void;
}

class HelloWorldCommand implements Command {
    execute(): void {
    	console.log("Hello World!");
    }
}

class Button {
    private command: Command;
    
    constructor(command: Command) {
        this.command = command;
    }
    
    click(): void {
        this.command.execute();
    }
}

Notice that now, the button has no idea of the command that it will be executing. All the button knows is that it has a command to run when it gets clicked. This makes it:

  • Easier to test: the button and the command are now separate, meaning we can write separate test coverage for both the button and the actual command.
  • Easier to swap functionality. This implementation is now more flexible: because the behaviour isn't hardcoded into the button class, we can create new commands that can be given to the button instead.

For a command to adhere to the command pattern, it should contain an execute() method. We've ensured that all commands passed to the button are valid commands by creating a Command interface.

Of course, the example given above is incredibly trivial. Let's take a look at a more in-depth example.

A Command Pattern Calculator

In this example, let's create a basic calculator. We will create a Commander to run the commands, a Calculator to keep the value, and concretions of the Command interface to represent operations we can perform.

Let's create a Commander class that has a run method. We will pass commands to this run method to execute them. This is similar to our Button example above, only rather than containing a command, this class is passed a command to execute. In reality, this might be a class that reads from a queue.

We'll also need to create a Calculator class, to actually hold the value that we will be incrementing.

class Commander {
  run(command: Command): void {
    command.execute();
  }
}

class Calculator {
  private total = 0;
  
  getTotal(): number {
    return this.total;
  }
  
  setTotal(total: number): void {
    this.total = total;
  }
}

Now, let's create an Add command, as well as a Command interface for us to implement.

interface Command {
  execute();
}

class AddCommand implements Command {
  execute(): void {
    // Something ?
  }
}

How should the execute method look? We can't pass in values, otherwise it won't adhere to the interface. However, our commands can store the values themselves. So, we should create a constructor for this command:

class AddCommand implements Command {
  private target: Counter;
  private amount: number;
  
  constructor(target: Calculator, amount: number) {
    this.target = target;
    this.amount = amount;
  }
  
  execute(): void {
    const newTotal = this.target.getTotal() + this.amount;
    
    this.target.setTotal(newTotal);
  }
}

const counter = new Calculator();

const add = new AddCommand(counter, 5);

Hopefully, you'll be able to use the example above to create classes for a SubtractCommand, MultiplyCommand and DivideCommand.

We can use these classes together like so:

const commander = new Commander();

const counter = new Calculator();

const add5 = new AddCommand(counter, 5);
const sub2 = new SubtractCommand(counter, 2);
const div2 = new DivideCommand(counter, 2);

commander->run(add5);
commander->run(add5);
commander->run(sub2);
commander->run(div2);

console.log(counter.getTotal()); // 4

This is a good place to introduce the concept of undoing commands. You can add a new method unexecute on your interface, and use this to revert any command by defining the inverse operation in your Command class.

For example, for the AddCommand:

class AddCommand implements Command {
  private target: Counter;
  private amount: number;
  
  constructor(target: Calculator, amount: number) {
    this.target = target;
    this.amount = amount;
  }
  
  execute(): void {
    const newTotal = this.target.getTotal() + this.amount;
    
    this.target.setTotal(newTotal);
  }
  
  unexecute(): void {
    const newTotal = this.target.getTotal() - this.amount;
    
    this.target.setTotal(newTotal);
  }
}

We can then introduce the concept of history to the commander, so that it can undo the last command:

class Commander {
  private history: Command[] = [];
  
  run(command: Command): void {
    command.execute();
    
    this.history.push(command);
  }
  
  undo(): void {
  	this.history.pop().unexecute();
  }
}
We've added a new history property to the class. In the run method, we now push the command to the history array. When we run undo, the last item in the history array is removed, and its unexecute method is ran.

Of course, not all commands are undoable, so bear this in mind when using this pattern.

Using Transactions with Macro Commands

Design Patterns also talks about Macro Commands. These commands allow you to run a number of commands in a single execution. This MacroCommand class implements the Command interface, meaning it can be passed in where ever you'd use a normal Command. To use it, you pass a number of commands in the constructor, and when the MacroCommand is executed, each of the commands it contains is executed.

class MacroCommand implements Command {
  private commands: Command[] = [];

  constructor(commands: Command[]) {
    this.commands = commands;
  }

  execute(): void {
    this.commands.forEach((c: Command) => c.execute());
  }

  unexecute(): void {
    const toUndo = [...this.commands];
    while (toUndo.length > 0) {
      toUndo.pop().unexecute();
    }
  }
}

Macro commands make it easier to create the idea of transactions in your code: if any step in the macro command fails, you can roll them all back. Either all succeed, or none do.

A really simple way of doing this could be to throw an exception inside a Command, and catch that exception inside the Macro Command. For example, you cannot divide by zero, so we should throw an exception if a user attempts to do that:

class DivideCommand implements Command {
  private target: Counter;
  private amount: number;
  
  constructor(target: Calculator, amount: number) {
    this.target = target;
    this.amount = amount;
  }
  
  execute(): void {
    if (this.amount === 0) {
      throw new Error("cannot divide by 0");
    }
    
    // ...
  }
  
  unexecute(): void {
    // ...
  }
}

Then, we can catch that error:

class MacroCommand implements Command {
  private commands: Command[] = [];
  private history: Command[] = [];

  constructor(commands: Command[]) {
    this.commands = commands;
  }

  execute(): void {
    try {
      this.commands.forEach((c: Command) => {
        c.execute();
        
        this.history.push(c);
      });
    } catch (err) {
      this.history.forEach((c: Command) => c.unexecute());
      
      return;
    }
  }

  unexecute(): void {
    const toUndo = [...this.commands];
    while (toUndo.length > 0) {
      toUndo.pop().unexecute();
    }
  }
}
We've added a history property to this class, so that we can keep track of which commands we have executed so far. If a command throws an exception, we can catch it and use the new history property to unexecute all the commands we've executed so far.

And that should just about do it! We now have a commander that can run commands and undo commands, and a calculator that stores values. We can pass the commander commands that perform operations on the calculator, as well as Macro commands, that run a chain of operations.

If you found this useful, please make sure you subscribe, so you'll be the first to know when the next Design Pattern writeup is published!