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.

Popular posts from this blog

Atom - Jupyter / Hydrogen

Design Patterns

Robson Koji Moriya disambiguation name