Chain of Responsibility: Decoupling

The Chain of Responsibility Pattern is a behavioral design pattern that decouples the sender and receiver of a request by composing multiple handlers into a chain for sequential processing. In this pattern, each handler can decide whether to process the request; if a handler cannot process it, the request is passed to the next handler in the chain until the request is handled.

In the Chain of Responsibility Pattern, every handler implements the same interface, allowing the client to send requests to the head of the chain. Each handler contains a reference to the next handler, enabling the sequential passing of requests. If a handler cannot process the request, it forwards it to the next handler until the request is successfully handled.

The implementation of the Chain of Responsibility Pattern typically includes the following roles:

  1. Abstract Handler Class: Defines the interface for processing requests and contains a reference to the next handler.
  2. Concrete Handler Class: Implements the interface defined by the abstract handler class and is responsible for processing requests.
  3. Client: Responsible for creating and sending requests, forwarding them to the head of the chain.

The core idea behind the Chain of Responsibility Pattern is to compose multiple handlers into a chain for sequential request processing. This pattern is suitable for scenarios where:

  1. The sender and receiver of a request need to be decoupled.
  2. The object responsible for handling a request needs to be dynamically specified.

The Chain of Responsibility Pattern makes systems more flexible and extensible because it decouples the sender and receiver of requests, allowing the system to dynamically specify which object handles the request. Additionally, this pattern can improve system performance, as each handler can choose whether to process the request, thereby avoiding unnecessary processing. However, the Chain of Responsibility Pattern may lead to unhandled requests if all handlers in the chain reject them.

class Handler:
    """Abstract Handler"""
    def __init__(self, next_handler=None):
        self.next_handler = next_handler
    
    def handle(self, request):
        # Logic to determine if this handler can process the request
        if self.can_handle(request):
            return self.process(request)
        else:
            # Pass the request to the next handler in the chain
            if self.next_handler is not None:
                return self.next_handler.handle(request)
            else:
                raise Exception("No one can handle this request")

class ConcreteHandlerA(Handler):
    """Concrete Handler A"""
    def can_handle(self, request):
        return request.type == "TYPE_A"
    
    def process(self, request):
        print(f"ConcreteHandlerA processing {request}")

class ConcreteHandlerB(Handler):
    """Concrete Handler B"""
    def can_handle(self, request):
        return request.type == "TYPE_B"
    
    def process(self, request):
        print(f"ConcreteHandlerB processing {request}")

# Client usage example
if __name__ == "__main__":
    # Create the chain: A -> B
    handler_a = ConcreteHandlerA()
    handler_b = ConcreteHandlerB()
    handler_a.next_handler = handler_b
    
    request1 = {"type": "TYPE_A"}
    request2 = {"type": "TYPE_B"}
    request3 = {"type": "TYPE_C"}
    
    # Send requests to the head of the chain
    handler_a.handle(request1)  # Handled by A
    handler_a.handle(request2)  # Handled by B
    handler_a.handle(request3)  # Unhandled (raises exception)