Mastering the Command Design Pattern

What is the Command Design Pattern?

Definition: The Command design pattern is a behavioural design pattern that encapsulates a request as an object, thereby allowing for the parameterization of clients with different requests, queue or log requests and supporting undoable operations.

Intent: It is primarily concerned with decoupling the sender of a request (client) from the receiver (the object that performs the action) by introducing an intermediary object (command) between them.

Analogy to remember 

Command design pattern analogy

In a restaurant, the waiter takes an order on a card and gives it to the chef, after that he brings the food. Order card is like a command given to the chef, based on the menu, he acted. While the customer(clients) are not aware by whom or how it will be cooked.

When to Use the Command Design Pattern

The Command Design Pattern is particularly useful in various scenarios:

  • Undo/Redo Functionality: When you want to implement undo and redo operations in applications, such as text editors or graphics software. Commands can store the state necessary to revert an action.
  • Queueing and Scheduling: In cases where you need to queue and schedule requests, like handling tasks in a multithreaded environment or managing user actions in a game.
  • Transaction Management: For managing database transactions or other actions that need to be treated as atomic operations.

Benefits of Using the Command Pattern

The Command Design Pattern offers several advantages in software development:

  • Decoupling of Components: It decouples the sender of a request (the client) from the object that performs the request (the receiver). This separation enhances modularity and maintainability.
  • Undo/Redo Support: The pattern naturally supports undo and redo functionality since each command can store the necessary information to reverse its execution.
  • Flexibility and Extensibility: New commands can be easily added without modifying existing code, promoting an open-closed principle.
  • Logging and Auditing: Commands can be logged, making it straightforward to track and audit user actions and system behaviour.
  • Support for Queuing and Scheduling: Commands can be easily queued and executed.

Implementing the Command Pattern

Let’s design home automation to switch on/off lights.

1st Approach, that comes into our mind

class Motor:
    def turn_on(self):
        print("Motor turned on")

class Light:
    def turn_on(self):
        print("Light turned on")

class Fan:
    def turn_on(self):
        print("Fan turned on")
        
# Client (Centralized Control Panel)
if __name__ == "__main__":
    lights = Light()
    motor  = Motor()
    fan    = Fan()
    lights.turn_on()
    motor.turn_on()
    fan.turn_on()

This will work perfectly, but the implementation has the following drawbacks

  • The client has direct knowledge of the devices and their methods
  • Tight coupling: The client has to modify its code if there is any updation in the smart device code.
  • Reduced Reusability: As the client is tightly coupled with smart devices, the reusability of the smart device’s code is difficult.

2nd Approach: Using the command design pattern

class Motor:
    def turn_on(self):
        print("Motor turned on")

class Light:
    def turn_on(self):
        print("Light turned on")

class Fan:
    def turn_on(self):
        print("Fan turned on")

class Command:
    def execute(self):
        pass

class TurnOnMoter(Command):
    def __init__(self, command):
        self.command = command
    def execute(self):
        self.command.turn_on()

class TurnOnFan(Command):
    def __init__(self,command):
        self.command = command
    def execute(self):
        self.command.turn_on()

class AutomationManager:
    def __init__(self):
        self.commands = ""
    def add_command(self,commands):
        self.commands = commands    
    
    def execute(self):
        self.commands.execute()

# Client (Centralized Control Panel)
if __name__ == "__main__":
    lights = Light()
    mot0r  = Motor()
    fan    = Fan()
    manger = AutomationManager() 
    
    manger.add_command(TurnOnMotor(motor))
    manger.execute()
    
    manger.add_command(TurnOnFan(fan))
    manger.execute()

This reduces the direct coupling of clients with device code

If you want additional functionality just add more commands. For Example, if we want both motor and lights to be on at the same time.

class TurnOnMotorAndFan(Command):
    def __init__(self,command1,command2):
        self.command1 = command1
        self.command2 =command2

    def execute(self):
        self.command1.turn_on()        
        self.command2.turn_on()  
manger.add_command(TurnOnMoterAndFan(motor,fan))
manger.execute()

This is the beauty of this design pattern add a command and you are good to go.

Real-World Examples

  • Game Development: Implementing game commands like move, attack, jump, and use items in a video game.
  • Restaurant Automation: Handling commands for automating the kitchen processes, such as cooking, serving, and billing.
  • Document Editing Application: Managing commands for formatting text, applying styles, and modifying document layout.
  • Flight Booking System: Handling commands for booking flights, managing reservations, and processing cancellations.
  • Smart Home Security System: Controlling security-related commands like arming, disarming, and triggering alarms in a smart home security application.
  • Music Player Application: Implementing music playback commands like play, pause, skip, and adjust volume in a music player.
  • Financial Trading Platform: Managing commands for executing trades, managing portfolios, and monitoring market data.
  • Task Scheduler: Implementing commands for scheduling and executing tasks at specific times or intervals.
  • Inventory Management System: Handling commands for managing inventory levels, restocking items, and processing orders.
  • Ticketing System: Managing commands for handling ticket bookings, cancellations, and seat reservations in a ticketing application.
  • Text Editor Application: Implementing text editing commands like insert, delete, copy, and paste.
  • Drawing and Graphics Software: Managing various drawing commands such as draw line, draw circle, fill shape, etc.
  • Undo/Redo Functionality: Enabling the ability to undo and redo user actions in different applications.
  • Home Automation System: Handling commands for controlling smart home devices, such as turning on/off lights, adjusting thermostats, etc.
  • Online Shopping Cart: Managing shopping cart operations like adding items, removing items, updating quantities, etc.
  • Restaurant Order Management: Implementing order commands for taking, processing, and serving customer orders.
  • Remote Control for Home Entertainment System: Controlling different functions of the home entertainment system, such as turning on/off devices, adjusting volume, etc.
  • Document Processing Application: Handling document commands like save, print, export, etc.
  • Robotics and Robotics Programming: Controlling robot movements and actions through command objects.
  • Traffic Light Control System: Managing traffic light commands to control traffic flow at intersections.

Let’s implement the logging of the request and undo feature



class Add:
    
    def execute(self,a,b):
        return a+b

    def undo(self,a,b):
        return a

class Multiple:
   
    def execute(self,a,b):
        return a*b
    
    def undo(self,a,b):
        return a

class addCommand():
   
    def __init__(self,command,a,b):
        self.command = command
        self.a = a
        self.b = b

    def execute(self):
        return self.command.execute(self.a,self.b)

    def undo(self):
        return self.command.undo(self.a,self.b)    

class mulCommand():
   
    def __init__(self,command,a,b):
        self.command = command
        self.a = a
        self.b = b

    def execute(self):
        return self.command.execute(self.a,self.b)

    def undo(self):
        return self.command.undo(self.a,self.b)    

class Calculator:

    def __init__(self):
        self.commands =[]

    def execute(self,command):
        self.commands.append(command)
        result = command.execute()
        print(result)
        return result

    def undo(self):
        last_command = self.commands.pop()
        result = last_command.undo()
        print(result)
        return result


add = Add()
mul =Multiple()
calculator = Calculator()
initial = 3
val = calculator.execute(addCommand(add,initial,4))  #7
val = calculator.undo() #3
val = calculator.execute(addCommand(add,val,2)) #5
val = calculator.execute(addCommand(add,val,5))#10
val = calculator.execute(addCommand(add,val,3))#13
val = calculator.execute(mulCommand(mul,val,3))#39 
val = calculator.undo() #13
val = calculator.execute(addCommand(add,val,7))#20
val = calculator.undo()#13
val = calculator.undo()#10
val = calculator.execute(mulCommand(mul,val,5))#50
val = calculator.undo()#10
val = calculator.undo()#5
val = calculator.undo()#3

We often interface with the command to have the same agreement 

class Command(ABC):
    @abstractmethod
    def execute(self):
        pass

    @abstractmethod
    def undo(self):
        pass

class addCommand(Command):
   
    def __init__(self,command,a,b):
        self.command = command
        self.a = a
        self.b = b

    def execute(self):
        return self.command.execute(self.a,self.b)

    def undo(self):
        return self.command.undo(self.a,self.b)   

For Video lover

Conclusion

In conclusion, the Command Design Pattern is a valuable tool in software development for encapsulating requests as objects, promoting flexibility, and decoupling the sender of a request from the receiver. By breaking down complex actions into separate command objects, developers can design more maintainable, extensible, and robust software systems. Some key takeaways from this article on the Command Pattern include:

  • Understanding the Command Pattern’s core concept of encapsulating actions as objects.
  • Recognizing its applications in scenarios such as remote controls, undo/redo functionality, and transaction management.
  • Appreciating the benefits of decoupling components, supporting undo/redo operations, and promoting extensibility.

To delve further into the Command Design Pattern and deepen your understanding of design patterns in general, consider exploring additional reading materials:

Leave a Comment