Command Pattern
Issue
The invoker (i.e. a button of a remote control) is supposed to be linked to various actions being executed when invoked
The amount and behaviour of these actions are open to change and must therefore be configurable
As a plus, the executed action(s) should be reversible
For example, the remote control’s “On” button is supposed to turn a TV on, but also turn it off, if it is already running.
Naming
- Client:
class that requests a command being executed
- Receiver:
class which actually performs the requested actions behind a command
- Invoker:
class that maps all possible command slots (i.e. button of a remote) to a certain command’s execute() method
- Command:
interface class that defines a structure of any concrete command class
- ConcreteCommand:
implementation of Command interface that calls certain functions on the Receiver upon being executed
Solution
Definition
The Command Pattern encapsulates a request as an object, thereby letting you parameterize other objects with different requests, queue or log requests, and support undoable operations.
classDiagram
Command "0..*" <-- Invoker : HAS-A
Command<|--ConcreteCommand : implements
Receiver<--ConcreteCommand : HAS-A
class Invoker {
+set_command(Command)
}
class Command {
<<interface>>
+execute()
+undo()
}
class Receiver {
+action_1()
+action_2()
}
class ConcreteCommand {
+execute()
+undo()
}
The Receiver contains all possible action methods (e.g. action_1(), action_2(), …) -> these are depending on the actual nature of the Receiver (i.e. a light might have an on() and off() method)
The Invoker has a Command interface instance for every “slot” that requires a Command being executed upon activation (i.e. a four button remote requires four Command instances)
The Invoker defines a method for calling each “slot”-Command’s execute() method (i.e. light_on_button_pressed())
The Command interface defines an execute() method and should also contain an undo() method to revert an executed command’s action
Any ConcreteCommand class contains a Receiver instance, which the execute() and undo() methods act upon
The Client is the one actually using the pattern:
it instantiates an Invoker (“You have access to the methods I want to execute”) -> HAS-A
it instantiates a Receiver (“You are the one I actually want to do something!”) -> HAS-A
- Routine:
The Client instantiates the Invoker, thereby passing the ConcreteCommand ‘s as arguments, which themselves take a receiver as an argument
The Client calls the Invoker’s method, that executes the corresponding command’s execute() method
Advantages:
Commands can execute other commands instead of directly acting on the Receiver (Macro-Commands like turning on multiple lights)
Undoing commands is easy to do -> enable the user to undo previous commands is often a smart way to structure and application, instead of just executing things
Example
Find a template example for Python at https://refactoring.guru/design-patterns/command/python/example
Hint
As an alternative to using “Command setter” methods in the Invoker class, you may pass the ConcreteCommand instances into the Invoker’s constructor and assign it right away.
It might be safer, not being able to reassign the commands in the invoker during runtime, but instead create multiple Invoker classes, which all assign commands differently. It also ensures, that Invokers cannot be instantiated without specifying all required commands.
from abc import ABC, abstractmethod
# Receiver
class FancyLight:
def turn_on(self):
print("Switching on ...")
return
def turn_off(self):
print("Switching off ...")
return
class Command(ABC):
@abstractmethod
def execute(self) -> None:
pass
@abstractmethod
def unexecute(self) -> None:
pass
# Concrete Commands
class TurnOnCommand(Command):
def __init__(self, light: FancyLight):
self.light = light
def execute(self) -> None:
self.light.turn_on()
def unexecute(self) -> None:
self.light.turn_off()
class TurnOffCommand(Command):
def __init__(self, light: FancyLight):
self.light = light
def execute(self) -> None:
self.light.turn_off()
def unexecute(self) -> None:
self.light.turn_on()
# Invoker
class SuperRemoteControl:
_turn_on = None
_turn_off = None
_light_state = None
_last_command = None
def __init__(self, turn_on_command, turn_off_command):
self._light_state = "off"
self._turn_on = turn_on_command
self._turn_off = turn_off_command
self._last_command = None
# Not needed as directly assigned in constructor (dependency injection)
def set_turn_on(self, command: Command):
self._turn_on = command
# Not needed as directly assigned in constructor (dependency injection)
def set_turn_off(self, command: Command):
self._turn_off = command
def press_onoff(self):
if self._light_state == "off":
self._turn_on.execute()
self._light_state = "on"
self._last_command = self._turn_on
elif self._light_state == "on":
self._turn_off.execute()
self._light_state = "off"
self._last_command = self._turn_off
def press_undo(self):
self._last_command.unexecute()
if __name__ == "__main__":
light = FancyLight()
control = SuperRemoteControl(
turn_on_command=TurnOnCommand(light),
turn_off_command=TurnOffCommand(light)
)
# Alternatively to passing commands into the constructor, they can be
# assigned in set-methods
# control.set_turn_on(TurnOnCommand(light))
# control.set_turn_off(TurnOffCommand(light))
control.press_onoff()
control.press_onoff()
control.press_undo()