Python Chain of Responsibility Design Pattern

1. Definition

The Chain of Responsibility Design Pattern decouples the sender from the receiver of a request by allowing more than one object to handle the request. The request is passed along a chain of potential handlers until one is found that can process the request or until the end of the chain is reached.

2. Problem Statement

Suppose you have various levels of logging (e.g., INFO, DEBUG, ERROR). Instead of implementing a monolithic piece of code to handle all these log levels, you'd like a system where each level is handled independently. Additionally, if a level can't handle a request, it should be able to delegate that request to the next level in the chain.

3. Solution

Implement a chain of objects where each object in the chain either handles the request or forwards it to the next object in the chain. Each handler decides whether to process the request or pass it along.

4. Real-World Use Cases

1. Event propagation in GUI libraries.

2. Middleware in web frameworks, where each middleware can decide to process the request or pass it along.

3. Workflow systems where a task moves from one stage to another.

5. Implementation Steps

1. Define a handler interface with a method to set the next handler and a method to handle the request.

2. Implement concrete handlers that decide whether to process the request or pass it to the next handler.

3. Chain the handlers in a specific order.

6. Implementation in Python

# Step 1: Define the handler interface
class Handler:
    def __init__(self):
        self.next_handler = None
    def set_next(self, handler):
        self.next_handler = handler
    def handle_request(self, request_type):
        pass
# Step 2: Implement concrete handlers
class InfoLogger(Handler):
    def handle_request(self, request_type):
        if request_type == "INFO":
            print("Logging INFO level message.")
        elif self.next_handler:
            self.next_handler.handle_request(request_type)
class DebugLogger(Handler):
    def handle_request(self, request_type):
        if request_type == "DEBUG":
            print("Logging DEBUG level message.")
        elif self.next_handler:
            self.next_handler.handle_request(request_type)
class ErrorLogger(Handler):
    def handle_request(self, request_type):
        if request_type == "ERROR":
            print("Logging ERROR level message.")
        elif self.next_handler:
            self.next_handler.handle_request(request_type)
# Step 3: Chain the handlers
info_logger = InfoLogger()
debug_logger = DebugLogger()
error_logger = ErrorLogger()
info_logger.set_next(debug_logger)
debug_logger.set_next(error_logger)
info_logger.handle_request("DEBUG")

Output:

"Logging DEBUG level message."

Explanation:

1. We define an abstract Handler with methods to set the next handler and handle a request.

2. Concrete implementations (like InfoLogger, DebugLogger, ErrorLogger) decide how to process the request.

3. In our example, when we send a "DEBUG" message, it first hits the InfoLogger. Since InfoLogger can't handle it, the message is passed to DebugLogger, which logs the message.

4. The pattern promotes loose coupling as the sender only communicates with the head of the chain, and handlers don't need to know about the inner workings of their successors.

7. When to use?

The Chain of Responsibility Pattern is beneficial when:

1. There are multiple ways to handle a request, but clear criteria exist on which handler is appropriate.

2. The set of handlers can dynamically change, or their sequence needs flexibility.

3. Decoupling of sender and receiver objects is required.


Comments