Chain of Responsibility Pattern
The Chain of Responsibility is a behavioral design pattern used in software development to process a request through a chain of handlers. Each handler in the chain either processes the request or passes it to the next handler. This pattern promotes loose coupling and flexibility by allowing an object to send a request without knowing which object will handle it. [1]
Here’s an overview of how the Chain of Responsibility pattern works:
- Handler Interface (or Abstract Class): Defines an interface for handling requests and optionally contains a reference to the next handler in the chain.
- Concrete Handlers: Implement the handler interface and process specific types of requests. Each concrete handler decides whether to handle the request or pass it along the chain.
- Client: Initiates the request and relies on the chain to handle it.
Key Components
- Handler: An interface or abstract class that declares a method for handling requests and holds a reference to the next handler.
- ConcreteHandler: A class that implements or extends
Handler
and processes requests it is responsible for. - Client: The entity that sends requests into the chain.
Example
Here's an example in Python:
from abc import ABC, abstractmethod
class Handler(ABC):
def __init__(self, successor=None):
self._successor = successor
@abstractmethod
def handle_request(self, request):
pass
class ConcreteHandler1(Handler):
def handle_request(self, request):
if 0 <= request < 10:
print(f"ConcreteHandler1 handled request {request}")
elif self._successor:
self._successor.handle_request(request)
class ConcreteHandler2(Handler):
def handle_request(self, request):
if 10 <= request < 20:
print(f"ConcreteHandler2 handled request {request}")
elif self._successor:
self._successor.handle_request(request)
class ConcreteHandler3(handler.Handler):
def handle_request(self, request):
if 20 <= request < 30:
print(f"ConcreteHandler3 handled request {request}")
elif self._successor:
self._successor.handle_request(request)
# Client code
if __name__ == "__main__":
# Create handlers
h3 = ConcreteHandler3()
h2 = ConcreteHandler2(h3)
h1 = ConcreteHandler1(h2)
# Send requests
requests = [5, 14, 22, 18]
for req in requests:
h1.handle_request(req)
Advantages
- Decoupling: The sender does not need to know which specific handler will process its request.
- Flexibility: Handlers can be added or removed easily without affecting other parts of the system.
- Responsibility Sharing: Multiple handlers can participate in processing a single type of request.
Disadvantages
- Potential for Unhandled Requests: If no handler in the chain is capable of handling a request, it may go unprocessed unless explicitly managed.
- Debugging Complexity: Tracing the flow of requests through multiple handlers can be challenging, making debugging more difficult.
- Performance Overhead: Passing a request along a long chain of handlers can introduce performance overhead, especially if many handlers are involved.
Use Cases
The Chain of Responsibility pattern is ideal for scenarios where:
- Multiple objects can handle a request, but the specific handler isn't known in advance.
- The set of handlers and their order can change dynamically.
- You want to decouple the sender and receiver to promote flexibility and reusability.
Real-World Examples
- Technical Support Systems: Customer support queries might be handled by different levels of support staff based on the complexity and type of query.
- Logging Systems: Different loggers (e.g., console logger, file logger, remote server logger) might handle log messages based on their severity level or type.
- Event Handling Systems: GUI frameworks often use this pattern to manage event handling where events are passed through a chain of handlers until one processes it.
Enhancing the Example
To make the example more robust and illustrate handling unprocessed requests, you could add a default handler at the end of the chain:
class DefaultHandler(Handler):
def handle_request(self, request):
print(f"Request {request} was not handled")
# Client code
if __name__ == "__main__":
# Create handlers with a default handler at the end
default_handler = DefaultHandler()
h3 = ConcreteHandler3(default_handler)
h2 = ConcreteHandler2(h3)
h1 = ConcreteHandler1(h2)
# Send requests
requests = [5, 14, 22, 18, 99]
for req in requests:
h1.handle_request(req)
In this enhanced version:
- A
DefaultHandler
is added to ensure that all requests are acknowledged even if no specific handler processes them. - The client code remains unchanged in terms of how it sends requests into the chain.
This pattern provides a flexible approach to processing requests by promoting loose coupling between request senders and receivers. By understanding its advantages and limitations, you can effectively apply it to suitable scenarios in your software design.