Usage examples: The Chain of Responsibility is pretty common in Python. It’s mostly relevant when your code operates with chains of objects, such as filters, event chains, etc.
Identification: The pattern is recognizable by behavioral methods of one group of objects that indirectly call the same methods in other objects, while all the objects follow the common interface.
Conceptual Example
This example illustrates the structure of the Chain of Responsibility design pattern. It focuses on answering these questions:
What classes does it consist of?
What roles do these classes play?
In what way the elements of the pattern are related?
main.py: Conceptual example
from__future__import annotationsfrom abc import ABC, abstractmethodfrom typing import Any, OptionalclassHandler(ABC):""" The Handler interface declares a method for building the chain of handlers. It also declares a method for executing a request. """@abstractmethoddefset_next(self,handler: Handler) -> Handler:pass@abstractmethoddefhandle(self,request) -> Optional[str]:passclassAbstractHandler(Handler):""" The default chaining behavior can be implemented inside a base handler class. """ _next_handler: Handler =Nonedefset_next(self,handler: Handler) -> Handler: self._next_handler = handler# Returning a handler from here will let us link handlers in a# convenient way like this:# monkey.set_next(squirrel).set_next(dog)return handler@abstractmethoddefhandle(self,request: Any) ->str:if self._next_handler:return self._next_handler.handle(request)returnNone"""All Concrete Handlers either handle a request or pass it to the next handler inthe chain."""classMonkeyHandler(AbstractHandler):defhandle(self,request: Any) ->str:if request =="Banana":returnf"Monkey: I'll eat the {request}"else:returnsuper().handle(request)classSquirrelHandler(AbstractHandler):defhandle(self,request: Any) ->str:if request =="Nut":returnf"Squirrel: I'll eat the {request}"else:returnsuper().handle(request)classDogHandler(AbstractHandler):defhandle(self,request: Any) ->str:if request =="MeatBall":returnf"Dog: I'll eat the {request}"else:returnsuper().handle(request)defclient_code(handler: Handler) ->None:""" The client code is usually suited to work with a single handler. In most cases, it is not even aware that the handler is part of a chain. """for food in ["Nut","Banana","Cup of coffee"]:print(f"\nClient: Who wants a {food}?") result = handler.handle(food)if result:print(f" {result}", end="")else:print(f" {food} was left untouched.", end="")if__name__=="__main__": monkey =MonkeyHandler() squirrel =SquirrelHandler() dog =DogHandler() monkey.set_next(squirrel).set_next(dog)# The client should be able to send a request to any handler, not just the# first one in the chain.print("Chain: Monkey > Squirrel > Dog")client_code(monkey)print("\n")print("Subchain: Squirrel > Dog")client_code(squirrel)
Output.txt: Execution result
Chain: Monkey > Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Monkey: I'll eat the Banana
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.
Subchain: Squirrel > Dog
Client: Who wants a Nut?
Squirrel: I'll eat the Nut
Client: Who wants a Banana?
Banana was left untouched.
Client: Who wants a Cup of coffee?
Cup of coffee was left untouched.