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() and set_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.