Command Design Pattern
Video Lecture
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
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
SBCODE Editor
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
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
| export default interface ICommand {
execute(): void
}
|
./src/command/iswitch-command.ts
| 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
SBCODE Editor
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.