8.11. 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.
8.11.1. Pattern
Chain of objects
Create a pipeline of classes with different responsibilities
Open/Close Principle for adding new handlers
8.11.2. Problem
8.11.3. Solution
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() == 'mwatney' and
request.get_password() == 'Ares3')
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('mwatney', 'Ares3'))
8.11.4. Assignments
Add Encryptor handler
Make pipeline: authenticator -> logger -> compressor -> encryptor