Chain of Responsibility Pattern
The Chain of Responsibility is a behavioral design pattern that allows a request to be passed along a chain of handlers, where each handler decides to process it or pass to the next. It decouples the sender from receivers, enabling dynamic handler ordering and avoiding if-else cascades.
Key Principles
- Handler Chain: Objects linked sequentially; each checks if it can handle the request.
- Loose Coupling: Sender unaware of handlers; handlers unaware of senders.
- Optional Handling: Requests can be handled by one, many, or none.
Structure
- Handler: Abstract class with
handle_request()andset_next(). - Concrete Handler: Implements handling logic; passes to next if unable.
- Client: Initiates request to first handler.
Python Example: Logging Middleware Chain
from abc import ABC, abstractmethod
# Handler Abstract Class
class Logger(ABC):
def __init__(self):
self._next = None
def set_next(self, handler) -> 'Logger':
self._next = handler
return handler
@abstractmethod
def handle_log(self, level: str, message: str):
pass
def _handle_next(self, level: str, message: str):
if self._next:
return self._next.handle_log(level, message)
print(f"[Unlogged] {level}: {message}")
# Concrete Handlers
class ErrorLogger(Logger):
def handle_log(self, level: str, message: str):
if level == "ERROR":
print(f"ERROR: {message}")
return
self._handle_next(level, message)
class WarningLogger(Logger):
def handle_log(self, level: str, message: str):
if level == "WARNING":
print(f"WARNING: {message}")
return
self._handle_next(level, message)
class InfoLogger(Logger):
def handle_log(self, level: str, message: str):
if level == "INFO":
print(f"INFO: {message}")
return
self._handle_next(level, message)
# Usage (Client Code)
if __name__ == "__main__":
# Build chain: Error → Warning → Info
error = ErrorLogger()
warning = WarningLogger()
info = InfoLogger()
chain = error.set_next(warning).set_next(info)
# Test requests
chain.handle_log("ERROR", "Disk full")
chain.handle_log("WARNING", "High CPU usage")
chain.handle_log("INFO", "User logged in")
chain.handle_log("DEBUG", "Minor event") # Falls through
Output:
ERROR: Disk full
WARNING: High CPU usage
INFO: User logged in
[Unlogged] DEBUG: Minor event
When to Use Chain of Responsibility
- Handler Pipelines: Logging levels, middleware (e.g., auth → validation → processing).
- Dynamic Ordering: Runtime handler configuration.
- Avoid: Fixed handlers (use strategy).
Summary: Chain of Responsibility links handlers in a sequence, passing requests until one processes it, promoting flexibility and decoupling.