Skip to content

Command Design Pattern

Video Lecture

Section Video Links
Command Pattern Command Command Pattern  
Command Use Case Command Use Case Command Use Case  

Overview

The Command pattern is a behavioral design pattern, in which an abstraction exists between an object that invokes a command, and the object that performs it.

E.g., a button will call the Invoker, that will call a pre-registered Command, that the Receiver will perform.

A Concrete Class will delegate a request to a command object, instead of implementing the request directly.

The command pattern is a good solution for implementing UNDO/REDO functionality into your application.

Uses:

  • GUI Buttons, menus
  • Macro recording
  • Multi-level undo/redo
  • Networking - send whole command objects across a network, even as a batch
  • Parallel processing or thread pools
  • Transactional behavior
  • Wizards

Terminology

  • Receiver: The object that will receive and execute the command.
  • Invoker: The object that sends the command to the receiver. E.g., A button.
  • Command Object: Itself, an object, that implements an execute(), or other action method, and contains all required information to execute it.
  • Client: The application or component that is aware of the Receiver, Invoker and Commands.

Command Pattern UML Diagram

The Command Pattern UML Diagram

Source Code

The Client instantiates a Receiver that accepts certain commands that do things.

The Client then creates two Command objects that will call one of the specific commands on the Receiver.

The Client then creates an Invoker, E.g., a user interface with buttons, and registers both Commands into the Invokers' dictionary of commands.

The Client doesn't call the receivers commands directly, but the via the Invoker, that then calls the registered Command objects execute() method.

This abstraction between the invoker, command and receiver, allows the Invoker to add extra functionality such as history, replay, UNDO/REDO, logging, alerting and any other useful things that may be required.

./src/command/command-concept.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
// The Command Pattern Concept

interface ICommand {
    execute(): void
}

class Invoker {
    // The Invoker Class
    #commands: { [id: string]: ICommand }

    constructor() {
        this.#commands = {}
    }

    register(commandName: string, command: ICommand) {
        // Register commands in the Invoker
        this.#commands[commandName] = command
    }

    execute(commandName: string) {
        // Execute any registered commands
        if (commandName in this.#commands) {
            this.#commands[commandName].execute()
        } else {
            console.log(`Command [${commandName}] not recognised`)
        }
    }
}

class Receiver {
    // The Receiver

    runCommand1() {
        // A set of instructions to run
        console.log('Executing Command 1')
    }

    runCommand2() {
        // A set of instructions to run
        console.log('Executing Command 2')
    }
}

class Command1 implements ICommand {
    // A Command object, that implements the ICommand interface and
    // runs the command on the designated receiver

    #receiver: Receiver

    constructor(receiver: Receiver) {
        this.#receiver = receiver
    }

    execute() {
        this.#receiver.runCommand1()
    }
}

class Command2 implements ICommand {
    // A Command object, that implements the ICommand interface and
    // runs the command on the designated receiver

    #receiver: Receiver

    constructor(receiver: Receiver) {
        this.#receiver = receiver
    }

    execute() {
        this.#receiver.runCommand2()
    }
}

// The Client
// Create a receiver
const RECEIVER = new Receiver()

// Create Commands
const COMMAND1 = new Command1(RECEIVER)
const COMMAND2 = new Command2(RECEIVER)

// Register the commands with the invoker
const INVOKER = new Invoker()
INVOKER.register('1', COMMAND1)
INVOKER.register('2', COMMAND2)

// Execute the commands that are registered on the Invoker
INVOKER.execute('1')
INVOKER.execute('2')
INVOKER.execute('1')
INVOKER.execute('2')

Output

node ./dist/command/command-concept.js
Executing Command 1
Executing Command 2
Executing Command 1
Executing Command 2

Command Use Case

This will be a smart light switch.

This light switch will keep a history of each time one of its commands was called.

And it can replay its commands.

A smart light switch could be extended in the future to be called remotely or automated depending on sensors.

Example UML Diagram

The Command Pattern UML Diagram

Source Code

./src/command/client.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// The Command Pattern Use Case Example. A smart light Switch

import Light from './light'
import Switch from './switch'
import SwitchOnCommand from './switch-on-command'
import SwitchOffCommand from './switch-off-command'

// Create a receiver
const LIGHT = new Light()

// Create Commands
const SWITCH_ON = new SwitchOnCommand(LIGHT)
const SWITCH_OFF = new SwitchOffCommand(LIGHT)

// Register the commands with the invoker
const SWITCH = new Switch()
SWITCH.register('ON', SWITCH_ON)
SWITCH.register('OFF', SWITCH_OFF)

// Execute the commands that are registered on the Invoker
SWITCH.execute('ON')
SWITCH.execute('OFF')
SWITCH.execute('ON')
SWITCH.execute('OFF')

// show history
SWITCH.showHistory()

// replay last two executed commands
SWITCH.replayLast(2)

./src/command/light.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
// The Light. The Receiver

export default class Light {
    turnOn(): void {
        // A set of instructions to run
        console.log('Light turned ON')
    }

    turnOff(): void {
        // A set of instructions to run
        console.log('Light turned OFF')
    }
}

./src/command/switch.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
// The Switch (Invoker) Class.

import ICommand from './icommand'

export default class Switch {
    #commands: { [id: string]: ICommand }
    #history: [number, string][]

    constructor() {
        this.#commands = {}
        this.#history = []
    }

    showHistory(): void {
        // Print the history of each time a command was invoked"
        this.#history.forEach((row) => {
            console.log(`${row[0]} : ${row[1]}`)
        })
    }

    register(commandName: string, command: ICommand): void {
        // Register commands in the Invoker
        this.#commands[commandName] = command
    }

    execute(commandName: string): void {
        // Execute any registered commands
        if (commandName in this.#commands) {
            this.#commands[commandName].execute()
            this.#history.push([Date.now(), commandName])
        } else {
            console.log(`Command [${commandName}] not recognised`)
        }
    }

    replayLast(numberOfCommands: number): void {
        // Replay the last N commands
        const commands = this.#history.slice(
            this.#history.length - numberOfCommands,
            this.#history.length
        )
        commands.forEach((command) => {
            this.#commands[command[1]].execute()
            // or if you wanted to also record this replay in history
            // this.execute(command[1])
        })
    }
}

./src/command/icommand.ts

1
2
3
export default interface ICommand {
    execute(): void
}

./src/command/iswitch-command.ts

1
2
3
export default interface ISwitchCommand {
    execute(commandName: string): void
}

./src/command/switch-on-command.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import ISwitchCommand from './iswitch-command'
import Light from './light'

export default class SwitchOnCommand implements ISwitchCommand {
    #light: Light

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

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

./src/command/switch-off-command.ts

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
import ISwitchCommand from './iswitch-command'
import Light from './light'

export default class SwitchOffCommand implements ISwitchCommand {
    #light: Light

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

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

Output

node ./dist/command/client.js
Light turned ON
Light turned OFF
Light turned ON
Light turned OFF
1619288201312 : ON
1619288201313 : OFF
1619288201313 : ON
1619288201313 : OFF
Light turned ON
Light turned OFF

Summary

  • State should not be managed in the Command object itself.
  • There can be one or more Invokers that can execute the Command at a later time.
  • The Command object is especially useful if you want to UNDO/REDO commands at later time.
  • The Command pattern is similar to the Memento pattern in the way that it can also be used for UNDO/REDO purposes. However, the Memento pattern is about recording and replacing the state of an object, whereas the Command pattern executes a predefined command. E.g., Draw, Turn, Resize, Save, etc.