Design Patterns - Command
Design Patterns - Command
Behavioral Pattern
The Command design pattern is a behavioral design pattern that encapsulates a request or action as an object, allowing you to parameterize clients with different requests, queue or log requests, and support undoable operations
The concrete command classes (TurnOnCommand and TurnOffCommand) implement the Command interface and are associated with the Light receiver. Each concrete command encapsulates a specific action.
This example demonstrates how the Command pattern separates the requester of an action (the invoker) from the object that performs the action (the receiver), allowing different commands to be executed dynamically.
from abc import ABC, abstractmethod
# Command interface
class Command(ABC):
@abstractmethod
def execute(self):
pass
# Receiver class
class Light:
def turn_on(self):
print("Light is on.")
def turn_off(self):
print("Light is off.")
# Concrete command classes
class TurnOnCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_on()
class TurnOffCommand(Command):
def __init__(self, light):
self.light = light
def execute(self):
self.light.turn_off()
# Invoker class
class RemoteControl:
def __init__(self):
self.command = None
def set_command(self, command):
self.command = command
def press_button(self):
if self.command:
self.command.execute()
# Client code
light = Light()
turn_on_command = TurnOnCommand(light)
turn_off_command = TurnOffCommand(light)
remote_control = RemoteControl()
# Turning on the light
remote_control.set_command(turn_on_command)
remote_control.press_button()
# Turning off the light
remote_control.set_command(turn_off_command)
remote_control.press_button()
Decoupling
When using the Command design pattern, the Receiver object is encapsulated within a Command object. The Command object acts as an intermediary, decoupling the requester (invoker) from the receiver.
By using the Command pattern, you gain flexibility and decoupling between the invoker and the receiver. You can dynamically change and execute different commands without explicitly calling the methods on the receiver. This allows for features like command queuing, logging, undo/redo functionality, or even storing and replaying sequences of commands.
Command Queuing
Command queuing allows you to queue multiple commands and execute them in a specific order.
# Creating a command queue
command_queue = []
# Adding commands to the queue
command_queue.append(TurnOnCommand(light))
command_queue.append(TurnOffCommand(light))
# Executing commands from the queue
for command in command_queue:
command.execute()
Logging
Logging commands allows you to keep a record of the executed commands for debugging or auditing purposes.
# Logging commands
logged_commands = []
# Executing and logging commands
turn_on_command.execute()
logged_commands.append(turn_on_command)
turn_off_command.execute()
logged_commands.append(turn_off_command)
# Accessing the logged commands
for command in logged_commands:
print(command)
Undo/Redo Functionality
The Command pattern can facilitate undo and redo operations by storing the state or parameters necessary to reverse or re-execute a command.
# Storing command history for undo/redo
command_history = []
# Executing commands
turn_on_command.execute()
command_history.append(turn_on_command)
# Undoing the last executed command
last_command = command_history.pop()
last_command.undo()
# Redoing the undone command
last_command.redo()
Storing and Replaying Sequences of Commands
The Command pattern allows you to store and replay sequences of commands to reproduce a specific behavior.
# Storing a sequence of commands
command_sequence = [
TurnOnCommand(light),
TurnOffCommand(light),
TurnOnCommand(light)
]
# Replaying the stored command sequence
for command in command_sequence:
command.execute()
SOLID
The Command design pattern can align with several principles from the SOLID design principles.
Single Responsibility Principle (SRP)
The Command pattern follows the SRP by separating the responsibilities of invoking an operation from the execution of that operation. The invoker (client or invoker object) is responsible for invoking the command, while the command object encapsulates the specific operation to be performed.
Open-Closed Principle (OCP)
The Command pattern supports the OCP by allowing new commands to be added without modifying existing client code or the invoker. You can introduce new command classes that implement the Command interface, extending the behavior of the system without requiring changes to existing code.
Liskov Substitution Principle (LSP)
The Command pattern is not directly related to the LSP, as it primarily deals with the decoupling of requesters (invokers) and executors (commands) rather than an inheritance hierarchy. However, the LSP can still apply to the design of individual commands if they are part of an inheritance hierarchy.
Interface Segregation Principle (ISP)
The Command pattern adheres to the ISP by employing a Command interface that defines a minimal set of methods needed for command execution. Clients or invokers interact with the Command interface, which only exposes the necessary methods, rather than being coupled to concrete command implementations.
Dependency Inversion Principle (DIP)
The Command pattern promotes the DIP by allowing the invoker (client) to depend on the Command interface (abstraction) rather than concrete command implementations (concrete classes). This abstraction decouples the client from the specific commands, enabling the client to work with different commands without directly depending on their concrete classes.