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 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.
./command/command_concept.py
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 | # pylint: disable=arguments-differ
"The Command Pattern Concept"
from abc import ABCMeta, abstractmethod
class ICommand(metaclass=ABCMeta): # pylint: disable=too-few-public-methods
"The command interface, that all commands will implement"
@staticmethod
@abstractmethod
def execute():
"The required execute method that all command objects will use"
class Invoker:
"The Invoker Class"
def __init__(self):
self._commands = {}
def register(self, command_name, command):
"Register commands in the Invoker"
self._commands[command_name] = command
def execute(self, command_name):
"Execute any registered commands"
if command_name in self._commands.keys():
self._commands[command_name].execute()
else:
print(f"Command [{command_name}] not recognised")
class Receiver:
"The Receiver"
@staticmethod
def run_command_1():
"A set of instructions to run"
print("Executing Command 1")
@staticmethod
def run_command_2():
"A set of instructions to run"
print("Executing Command 2")
class Command1(ICommand): # pylint: disable=too-few-public-methods
"""A Command object, that implements the ICommand interface and
runs the command on the designated receiver"""
def __init__(self, receiver):
self._receiver = receiver
def execute(self):
self._receiver.run_command_1()
class Command2(ICommand): # pylint: disable=too-few-public-methods
"""A Command object, that implements the ICommand interface and
runs the command on the designated receiver"""
def __init__(self, receiver):
self._receiver = receiver
def execute(self):
self._receiver.run_command_2()
# The CLient
# Create a receiver
RECEIVER = Receiver()
# Create Commands
COMMAND1 = Command1(RECEIVER)
COMMAND2 = Command2(RECEIVER)
# Register the commands with the invoker
INVOKER = 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
python ./command/command_concept.py
Executing Command 1
Executing Command 2
Executing Command 1
Executing Command 2
SBCODE Editor
Example 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
./command/client.py
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 | "The Command Pattern Use Case Example. A smart light Switch"
from light import Light
from switch import Switch
from switch_on_command import SwitchOnCommand
from switch_off_command import SwitchOffCommand
# Create a receiver
LIGHT = Light()
# Create Commands
SWITCH_ON = SwitchOnCommand(LIGHT)
SWITCH_OFF = SwitchOffCommand(LIGHT)
# Register the commands with the invoker
SWITCH = 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.show_history()
# replay last two executed commands
SWITCH.replay_last(2)
|
./command/light.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | "The Light. The Receiver"
class Light:
"The Receiver"
@staticmethod
def turn_on():
"A set of instructions to run"
print("Light turned ON")
@staticmethod
def turn_off():
"A set of instructions to run"
print("Light turned OFF")
|
./command/switch.py
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 | """
The Switch (Invoker) Class.
You can flick the switch and it then invokes a registered command
"""
from datetime import datetime
import time
class Switch:
"The Invoker Class."
def __init__(self):
self._commands = {}
self._history = []
def show_history(self):
"Print the history of each time a command was invoked"
for row in self._history:
print(
f"{datetime.fromtimestamp(row[0]).strftime('%H:%M:%S')}"
f" : {row[1]}"
)
def register(self, command_name, command):
"Register commands in the Invoker"
self._commands[command_name] = command
def execute(self, command_name):
"Execute any registered commands"
if command_name in self._commands.keys():
self._commands[command_name].execute()
self._history.append((time.time(), command_name))
else:
print(f"Command [{command_name}] not recognised")
def replay_last(self, number_of_commands):
"Replay the last N commands"
commands = self._history[-number_of_commands:]
for command in commands:
self._commands[command[1]].execute()
#or if you want to record these replays in history
#self.execute(command[1])
|
./command/switch_on_command.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | """
A Command object, that implements the ISwitch interface and runs the
command on the designated receiver
"""
from interface_switch import ISwitch
class SwitchOnCommand(ISwitch): # pylint: disable=too-few-public-methods
"Switch On Command"
def __init__(self, light):
self._light = light
def execute(self):
self._light.turn_on()
|
./command/switch_off_command.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | """
A Command object, that implements the ISwitch interface and runs the
command on the designated receiver
"""
from interface_switch import ISwitch
class SwitchOffCommand(ISwitch): # pylint: disable=too-few-public-methods
"Switch Off Command"
def __init__(self, light):
self._light = light
def execute(self):
self._light.turn_off()
|
./command/interface_switch.py
| "The switch interface, that all commands will implement"
from abc import ABCMeta, abstractmethod
class ISwitch(metaclass=ABCMeta): # pylint: disable=too-few-public-methods
"The switch interface, that all commands will implement"
@staticmethod
@abstractmethod
def execute():
"The required execute method that all command objects will use"
|
Output
python ./command/client.py
Light turned ON
Light turned OFF
Light turned ON
Light turned OFF
11:23:35 : ON
11:23:35 : OFF
11:23:35 : ON
11:23:35 : OFF
Light turned ON
Light turned OFF
SBCODE Editor
New Coding Concepts
_Single Leading Underscore
The single leading underscore _variable
, on your class variables is a useful indicator to other developers that this property should be considered private.
Private, in C style languages, means that the variable/field/property is hidden and cannot be accessed outside the class. It can only be used internally by its own class methods.
Python does not have a public/private accessor concept, so the variable is not actually private and can still be used outside the class in other modules.
It is just a useful construct that you will see developers use as a recommendation not to reference this variable directly outside this class, but use a dedicated method or property instead.
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.