6.9. Chain of Responsibility

  • EN: Chain of Responsibility

  • PL: Łańcuch zobowiązań

  • Type: object

The Chain of Responsibility design pattern is a behavioral design pattern that allows an object to pass the request along the chain of potential handlers until an object handles it. It simplifies your code and allows you to skip explicit sender-receiver bindings.

In Python, we can implement the Chain of Responsibility pattern using classes. Here's a simple example:

First, we define a Handler class that declares an abstract handle method and a method to set the next handler:

>>> class Handler:
...     def __init__(self):
...         self._next_handler = None
...
...     def set_next(self, handler):
...         self._next_handler = handler
...         return handler
...
...     def handle(self, request):
...         if self._next_handler:
...             return self._next_handler.handle(request)
...         return None

Then, we define a ConcreteHandler class that implements the handle method:

>>> class ConcreteHandler1(Handler):
...     def handle(self, request):
...         if request == "request1":
...             return "Handler1"
...         else:
...             return super().handle(request)

Finally, we can use the Handler and ConcreteHandler classes like this:

>>> handler1 = ConcreteHandler1()
>>> handler2 = ConcreteHandler1()
...
>>> handler1.set_next(handler2)  
<__main__.ConcreteHandler1 object at 0x...>
>>> print(handler1.handle("request1"))
Handler1
>>> print(handler1.handle("request2"))
None

In this example, the ConcreteHandler1 handles the request if it can, otherwise it passes the request to the next handler in the chain.

6.9.1. Pattern

  • Chain of objects

  • Create a pipeline of classes with different responsibilities

  • Open/Close Principle for adding new handlers

../../_images/designpatterns-chainofresponsibility-pattern.png

6.9.2. Problem

../../_images/designpatterns-chainofresponsibility-problem.png


6.9.3. Solution

../../_images/designpatterns-chainofresponsibility-solution.png

from abc import ABC, abstractmethod
from dataclasses import dataclass


@dataclass
class HttpRequests:
    username: str
    password: str

    def get_username(self) -> str:
        return self.username

    def get_password(self) -> str:
        return self.password


@dataclass
class Handler(ABC):
    next: 'Handler'

    @abstractmethod
    def do_handle(self, request: HttpRequests) -> bool:
        pass

    def handle(self, request: HttpRequests) -> None:
        if self.do_handle(request):
            return
        if self.next:
            self.next.handle(request)


class Authenticator(Handler):
    def do_handle(self, request: HttpRequests) -> bool:
        is_valid: bool = (request.get_username() == 'admin' and
                          request.get_password() == 'myVoiceIsMyPassword')
        print('Authentication')
        return not is_valid


class Compressor(Handler):
    def do_handle(self, request: HttpRequests) -> bool:
        print('Compress')


class Logger(Handler):
    def do_handle(self, request: HttpRequests) -> bool:
        print('Log')


@dataclass
class WebServer:
    handler: Handler

    def handle(self, request: HttpRequests) -> None:
        self.handler.handle(request)


if __name__ == '__main__':
    # authenticator -> logger -> compressor
    compressor: Compressor = Compressor(None)
    logger: Logger = Logger(compressor)
    authenticator: Authenticator = Authenticator(logger)
    server = WebServer(authenticator)
    server.handle(HttpRequests('admin', 'myVoiceIsMyPassword'))

6.9.4. Assignments

  • Add Encryptor handler

  • Make pipeline: authenticator -> logger -> compressor -> encryptor