Chain of responsibility pattern in python

Behavioral design pattern in python

Definition: Behavioral design patterns are responsible for identifying communication patterns between various existing objects. This identification improves the flexibility of this Communication. This design pattern is concerned with the assignments of responsibility for different objects. Here hard coding is avoided, and the client remains loosely coupled.

Chain of responsibility :

This is one behavioral design pattern method that helps to pass requests along with the process handlers. It consists of a chain of handlers that have some associated responsibility. That's why it is called a chain of responsibility. Whenever a request is passed, the handler decides which of the handler among the existing ones will process the request. Simply put, each handler or receiver has the reference to the next one, so if they cannot execute the request action, they pass the request to the next handler. It is somewhat similar to the else if conditions of programing languages(c,c++, python, etc.), which is executed only if the condition is true, which means there is a predetermined condition that needs to be true to be executed by a specific handler. The requests are sorted or filtered by their type and then passed to the particular handler for execution. This was the basic principle of working on a chain of responsibility.

Problem statement :

Suppose you are the founder of an e-commerce book-selling platform. After months of work, your website is finally created. Now your concern is with the authentication of the user. To maintain the website's authentication, you must add only authentic buyers and sellers. So first, a request made is from the client side, and if the provided information is correct, then their authentication approves. This process must be done in sequential order. The authenticated users must have access to the full website. If the information provided by the user is incorrect, then there is no further need to continue the process. The process terminates right there. Till now, you were passing raw data, but you can know that passing raw data is not safe, so extra filtering of data is done. However, there is one more problem, the system is still not safe because anybody can access it by using brute force password cracking. To avoid this, you added the feature of blocking if any single IP address has multiple failures within a short period. Everything is going well, but many users joined your websites, so there is now a speed problem. Somebody suggested you keep cache to boost the overall speed of the software as speed is one of the important criteria to make the website efficient. So you added a cache feature, so now, for repeating requests, the system will provide answers frequently from the cache. You might think the website is now perfect but stop and think once! Now our code has become vulnerable. For each newly added feature, the code became messier and loaded with unorganized data, changing one feature affected the other, and many more. So our system has become a disaster and tough to maintain and extend, and now if you want to add a single feature, it would require changes in the whole system.

Adding features is inevitable. We have to introduce new features from time to time, but we need to make our system capable of adding features without interruption, and the code must be clean! So how can this be done? How can we add all the features without affecting the existing feature and code?

Solution statement:   

To solve this problem, we will create stand-alone objects called handlers; we will make a chain of handlers that will decide whether to execute the handler or pass this to the next one. Assuming all the information provided is correct, then the handler will execute the process; otherwise, it will be terminated. This makes the code less bulky and clean. We do not need to make changes to the existing objects or classes. We will create handlers for adding features, and as per request, the handler will execute the necessary operation; hence, our problem is solved.

Example.py:

class Abstract_Handler(object):
  """concrete handlers have one parent class which is a parent for all and in this it's abstract """
  def __init__(self, nxt):
    """local variables are modified as per requirement using the next """
    self._nxt = nxt
  def handle1(self, request):
    """the given request activates the process requests by their separate methods"""
    handled = self.processRequest(request)
    """when its failed to handle the case then below will be executes"""
    if not handled:
      self._nxt.handle1(request)
  def processRequest(self, request):
    """NotImplementedError is thrown here if called"""
    raise NotImplementedError('First implement it !')
class FirstHandler(Abstract_Handler):
  """this is the  Child class of our one and only parent """
  def processRequest(self, request):
    '''it will be returning the true value if the request is handled by the above condition'''
    if 'a' < request <= 'e':
      print("This is {} handling request '{}'".format(self.__class__.__name__, request))
      return True
class SecondHandler(Abstract_Handler):
  """this is the  Child class of our one and only parent """
  def processRequest(self, request):
    '''it will be returning the true value if the request is handled by the above condition'''
   if 'e' < request <= 'l':
      print("This is {} handling request '{}'".format(self.__class__.__name__, request))
      return True
class Thirdhandle(Abstract_Handler):
 def processRequest(self, request):
    if 'a' < request <= 'z':
      print("This is {} handling request '{}'".format(self.__class__.__name__, request))
      return True
class Default(Abstract_Handler):
  """ this will be called if none of the handlers can handle the case"""
  def processRequest(self, request):
    """Gives the message that th request is not handled and returns true"""


    print("This is {} telling you that request '{}' has no handler right now.".format(self.__class__.__name__,
                                            request))
    return True
class User1:
  """User Class"""
  def __init__(self):
    """this gives us the sequence in which the user will have the handlers """
    initial = None
    self.handler = FirstHandler(SecondHandler(Thirdhandle(Default(initial))))
  def agent(self, user1_request):
    """this helps in deciding the handlers, and it iterates over all the handlers """
    for request in user1_request:
      self.handler.handle1(request)
"""this is our main method"""
if __name__ == "__main__":
  """this creates a client object which helps the user in several ways """
  user1 = User1()
  """to proceed further, the necessary requests are made by the user and request method """
  string12 = "tutorialandexample"
  requests = list(string12)
  """Send the requests one by one to handlers as per the sequence of handlers defined in the Client class"""
  user1.agent(requests)

Output.txt:

This is Thirdhandle handling request 't'
This is Thirdhandle handling request 'u'
This is Thirdhandle handling request 't'
This is Thirdhandle handling request 'o'
This is Thirdhandle handling request 'r'
This is SecondHandler handling request 'i'
This is Default telling you that request 'a' has no handler right now.
This is SecondHandler handling request 'l'
This is Default telling you that request 'a' has no handler right now.
This is Thirdhandle handling request 'n'
This is FirstHandler handling request 'd'
This is FirstHandler handling request 'e'
This is Thirdhandle handling request 'x'
This is Default telling you that request 'a' has no handler right now.
This is Thirdhandle handling request 'm'
This is Thirdhandle handling request 'p'
This is SecondHandler handling request 'l'
This is FirstHandler handling request 'e'

Explanation :

Suppose you have decided that whenever a string is inserted into the website, it must go through some checks, so adding them as per the conventional method is not worth it. So you created a handler for solving this problem, and they are chained sequentially. This is the solution for the various checks of a string.

One parent handler is created, which will maintain all the handlers and helps to execute the tasks and control the activities.

Parent class :

class Abstract_Handler(object):
  """concrete handlers have one parent class which is a parent for all, and in this it's abstract """
  def __init__(self, nxt):
    """local variables are modified as per requirement using the next """
    self._nxt = nxt

This is our parent class, and we have declared one handler inside the code, which will have control over all the existing handlers. We have created a total of three handlers, and suppose if we pass a request, it will be handed to all the handlers until it is executed. At last, if all the handlers cannot execute, it will move forward to the default class, which will validate the data and check whether the information provided is correct.

Advantages of Chain of Responsibility Pattern

  • The coupling is reduced.
  • Assigning responsibilities to objects becomes more flexible.
  • It allows a set of classes to act as one; events produced in one class can be sent to other handler classes with the help of composition.

Disadvantages of the chain of responsibility pattern :

  • It might increase the code complexity.



ADVERTISEMENT
ADVERTISEMENT