Python Design Patterns - Command Pattern


Command pattern is a behavioural design pattern in which the object is encapsulate all the information to be executed at later time.

To understand this pattern let’s take an example of signing up for an account.

So let’s assume that registration is three step procedure. First step is to fill basic details, second step to fill business details and then final step to fill payment details. Initially object of Command is created and will be used to collect all information. Once all information of these three steps are filled you would hit Register button and Command object which has encapsulated all the information in it and execute method will be executed to register the account.

Useful terms

  • Command - Executes receiver’s action on invoking by Invoker
  • Client: Create the Invoker object and also sets the receiver object
  • Invoker: This actually manage the commands by specific invoke method.
  • Receiver: Ultimate action performer.

Understanding of command pattern —

  • Object of Command has knowledge of Receiver so that can call method of Receiver object. All the values will be stored in object of Command class and those values will be passed to Receiver object method.
  • Invoker has details to execute command.
  • Client object passes a Receiver object while creating Command object.

How to implement —

'''
Command Design Pattern (Behavioral Design Pattern)
'''
from abc import ABC, abstractmethod


class BaseCommannd(ABC):
    '''
    Base command class
    '''

    @abstractmethod
    def execute(self):
        raise NotImplementedError("Please implement in subclass")

class EMailCommand(BaseCommannd):
    '''
    Email Command class
    '''
    def __init__(self, receiver, data):
        self.receiver = receiver
        self.data = data

    def execute(self):
        self.receiver.send_email(self.data)


class SMSCommand(object):
    '''
    Command class
    '''
    def __init__(self, receiver, data):
        self.receiver = receiver
        self.data = data

    def execute(self):
        self.receiver.send_sms(self.data)

class NotificationService(object):
    '''
    Receiver class
    '''
    def send_email(self, data):
        print("Sending email", data)

    def send_sms(self, data):
        print("Sending short message", data)

class NotificationInvoker(object):
    '''
    Invoker class
    ''' 
    def __init__(self):
        self.notification_history = []

    def invoke(self, command):
        self.notification_history.append(command)
        command.execute()

if __name__ == "__main__":
    invoker = NotificationInvoker()
    sender = NotificationService()
    invoker.invoke(EMailCommand(sender, {"subject": "Test Email"}))
    invoker.invoke(SMSCommand(sender, {"subject": "Test SMS"}))
    print(invoker.notification_history)

In above example,

NotificationService (Receiver) is ultimate executer of the action.

BaseCommand is the command interface. We have two concrete command class — SMSCommand and EMailCommand

NotificationInvoker is invoker class which actually call the execute method of the command object. Here all the commands are stored in notification_history object. Which cab be used to re-execute all the commands as those command object contains all the information to execute again.

Here, Client class is not implemented as we are the client using all above classes as all steps are mentioned in end of the code.

First, we create objects of Receiver and Invoker class. We also create objects of EMailCommand or SMSCommand class with passing arguments and pass it to invoke method of Invoker class object. When you invoke any command Invoker will call execute method of command object and command will call the method of Receiver object with all the information are stored in Command object.

In above example, We are executing methods on instant basis. But we can modify Invoker where we can set scheduling information that way we can make it asynchronous. We can also modify Command classes where we can provide facility to consolidate all the information in different way.

This pattern can be used in our projects with few tweaks according to your needs.

Benefits of this pattern —

  • Re-perform or Rollback operation can be performed.
  • Queue system can be built to execute commands in sequence.
  • New type of command can be introduced with very few changes.
blog comments powered by Disqus