Easy patterns in Python: Chain of Responsibility

Introduction

The Chain of Responsibility pattern describes a chain of command/request receivers. The client has no idea which one of the receivers will handle the request. The beauty of the pattern is again uncoupling: the sender and the receiver do not know about each other. A danger is that no receiver in the chain will handle the request, this has to be taken into account when implementing this pattern.

An excellent example of this could be the way that middleware is implemented in some web-frameworks (Gin, but also ASP.NET core are examples).

It looks like this:

Let us break this down:

  1. We have a Client who does a request to a Handler
  2. The Handler has a list of Receivers, and succesively sends the request to a receiver until one handles the request

As you can see, this is not a very complicated pattern

Implementation in Python

Since we will implement a class that will be referring to itself, we need the following opening line:

from __future__ import annotations

We will start by defining the Handler base class:

class Handler:
    def set_next(self, next_handler: Handler):
        pass

    def handle(self, request: str):
        pass

In this class we define two methods which do nothing at this moment:

  1. set_next() sets the next handler to handle the request.
  2. handle() is used to perform the actual action. In our case that is just printing out a string

Next we define the first handler imaginatively called HandlerA:

class HandlerA(Handler):
    _next_handler: Handler = None

    def handle(self, request: str):
        if request == 'A':
            print(f"HandlerA handles request {request}")
        else:
            if self._next_handler is not None:
                self._next_handler.handle(request)

    def set_next(self, next_handler: Handler):
        self._next_handler = next_handler

A short breakdown:

  • In the set_next() method we set the handler to the given handler
  • The handle() method handles some requests, and on other it passes the request to the next handler in the line.

We also have a similar HandlerB class:

class HandlerB(Handler):
    _next_handler: Handler = None

    def handle(self, request: str):
        if request == 'B':
            print(f"HandlerB handles request {request}")
        else:
            if self._next_handler is not None:
                self._next_handler.handle(request)

    def set_next(self, next_handler: Handler):
        self._next_handler = next_handler

Note that both handlers derive from the Handler class, so we can call both the set_next() and the handle() methods.

Time to test

Now we can see whether our setup works:

if __name__ == "__main__":
    handler_a: HandlerA = HandlerA()
    handler_b: HandlerB = HandlerB()
    handler_a.set_next(handler_b)
    handler_a.handle('A')
    handler_a.handle('B')
    handler_a.handle('C')

A line by line explanation:

  • We construct two handlers, of type HandlerA and HandlerB.
  • We set handler_b as the next in line from handler_a

Next we send three requests:

  1. If we send an ‘A’, only handler_a handles that.
  2. If send a ‘B’, it passes through A, since it doesn’t know how to handle that, and handler_b handles it.
  3. The request ‘C’ is not recognized by either handler, and therefore ultimately ignored.

Conclusion

The Chain of Responsibility pattern is a very easy way to have different receivers handle the same request. In a further blog we will see how we make this multithreaded (under certain circumstances this is possible)

The implementation in Python was quite straightforward and easy.

Leave a Reply

Your email address will not be published. Required fields are marked *