Chain of Responsibility

The Chain of Responsibility is a behavioral design pattern that allows you to pass requests along a chain of handlers. Each handler in the chain decides whether to process the request or pass it to the next handler in the sequence. This pattern promotes loose coupling between the sender of a request and its receivers, giving multiple objects a chance to handle the request.

Core Concepts

The pattern works by creating a chain of receiver objects for a request. Each receiver contains a reference to the next receiver in the chain. If one receiver cannot handle the request, it passes the request to the next receiver, and so on, until either the request is handled or the end of the chain is reached.

Key Components

The Chain of Responsibility pattern consists of four main components:

  • Handler Interface: Defines the interface for handling requests and optionally implements the successor link.
  • Concrete Handlers: Handle requests they are responsible for and forward requests they cannot handle to the successor.
  • Client: Initiates the request to a handler object in the chain.
  • Request: The object being passed through the chain, containing the data needed for processing.

Pattern Flow

Client Request → Handler1 → Handler2 → Handler3 → ... → HandlerN
                    ↓         ↓         ↓              ↓
                 Process    Process   Process       Process
                 or Pass    or Pass   or Pass       or End

When to Use

The Chain of Responsibility pattern is particularly useful when:

  • You have multiple objects that can handle a request, but you don’t know which one will handle it at runtime
  • You want to issue a request to one of several objects without specifying the receiver explicitly
  • The set of objects that can handle a request should be specified dynamically
  • You want to decouple the sender of a request from its receivers

Benefits

  • Decoupling: The pattern decouples the sender from the receiver, as the sender doesn’t need to know which specific handler will process the request.
  • Flexibility: You can add or remove handlers dynamically without changing the client code.
  • Responsibility Distribution: Each handler focuses on a specific type of request, following the Single Responsibility Principle.
  • Chain Modification: The order of handlers in the chain can be modified at runtime.

Drawbacks

  • Performance: The request might travel through many handlers before finding the appropriate one, potentially impacting performance.
  • No Guarantee: There’s no guarantee that a request will be handled if no handler in the chain can process it.
  • Debugging Complexity: Tracing the flow through multiple handlers can make debugging more challenging.

Common Use Cases

The Chain of Responsibility pattern appears in many real-world scenarios:

  • Web Request Processing: Web frameworks often use this pattern to handle HTTP requests through middleware layers.
  • Event Handling: GUI frameworks use this pattern to handle user interface events.
  • Logging Systems: Different log levels (DEBUG, INFO, WARN, ERROR) can be handled by different loggers in a chain.
  • Authentication and Authorization: Security systems can chain different authentication methods.
  • Command Processing: Different types of commands can be processed by appropriate handlers in the chain.

Implementation Strategy

When implementing the Chain of Responsibility pattern, consider these key aspects:

  • Handler Interface: Create a common interface that all handlers implement, including methods for handling requests and setting the next handler.
  • Chain Setup: Establish the chain by linking handlers together, typically done during initialization.
  • Request Propagation: Ensure that each handler either processes the request or passes it to the next handler.
  • Termination Condition: Define what happens when the end of the chain is reached without the request being handled.

The Chain of Responsibility pattern provides an elegant solution for scenarios where multiple objects might handle a request, promoting flexibility and maintainability in your code architecture.

Example: Customer Support System

This example demonstrates the Chain of Responsibility pattern through a customer support ticket system. The implementation showcases how different levels of support staff can handle requests based on their priority levels.

from abc import ABC, abstractmethod
from enum import Enum
from typing import Optional, Dict, Any

class Priority(Enum):
    """Enumeration for support request priorities."""
    LOW = 1
    MEDIUM = 2
    HIGH = 3
    CRITICAL = 4

class SupportRequest:
    """
    Represents a customer support request that will be passed through the chain.
    """
    
    def __init__(self, request_id: str, customer_name: str, 
                 issue_description: str, priority: Priority):
        self.request_id = request_id
        self.customer_name = customer_name
        self.issue_description = issue_description
        self.priority = priority
        self.resolution: Optional[str] = None
        self.handled_by: Optional[str] = None
    
    def __str__(self) -> str:
        return f"Request {self.request_id}: {self.issue_description} (Priority: {self.priority.name})"

class SupportHandler(ABC):
    """
    Abstract base class for all support handlers in the chain.
    Defines the interface for handling support requests.
    """
    
    def __init__(self, handler_name: str):
        self.handler_name = handler_name
        self._next_handler: Optional[SupportHandler] = None
    
    def set_next(self, handler: 'SupportHandler') -> 'SupportHandler':
        """
        Sets the next handler in the chain.
        Returns the handler for method chaining.
        """
        self._next_handler = handler
        return handler
    
    def handle_request(self, request: SupportRequest) -> bool:
        """
        Main method to handle the request.
        Returns True if handled, False otherwise.
        """
        if self._can_handle(request):
            self._process_request(request)
            return True
        
        # Pass to next handler if available
        if self._next_handler:
            return self._next_handler.handle_request(request)
        
        return False
    
    @abstractmethod
    def _can_handle(self, request: SupportRequest) -> bool:
        """
        Determines if this handler can process the given request.
        Must be implemented by concrete handlers.
        """
        pass
    
    @abstractmethod
    def _process_request(self, request: SupportRequest) -> None:
        """
        Processes the request when this handler can handle it.
        Must be implemented by concrete handlers.
        """
        pass

class Level1Support(SupportHandler):
    """
    First level support handler - handles basic and low priority issues.
    """
    
    def __init__(self):
        super().__init__("Level 1 Support")
    
    def _can_handle(self, request: SupportRequest) -> bool:
        """Level 1 handles LOW priority requests."""
        return request.priority == Priority.LOW
    
    def _process_request(self, request: SupportRequest) -> None:
        """Process basic support requests."""
        request.handled_by = self.handler_name
        request.resolution = "Provided standard troubleshooting steps and FAQ links"
        print(f"[{self.handler_name}] Handled: {request}")

class Level2Support(SupportHandler):
    """
    Second level support handler - handles medium priority technical issues.
    """
    
    def __init__(self):
        super().__init__("Level 2 Support")
    
    def _can_handle(self, request: SupportRequest) -> bool:
        """Level 2 handles MEDIUM priority requests."""
        return request.priority == Priority.MEDIUM
    
    def _process_request(self, request: SupportRequest) -> None:
        """Process intermediate technical support requests."""
        request.handled_by = self.handler_name
        request.resolution = "Performed advanced troubleshooting and provided technical solution"
        print(f"[{self.handler_name}] Handled: {request}")

class Level3Support(SupportHandler):
    """
    Third level support handler - handles high priority and complex issues.
    """
    
    def __init__(self):
        super().__init__("Level 3 Support")
    
    def _can_handle(self, request: SupportRequest) -> bool:
        """Level 3 handles HIGH priority requests."""
        return request.priority == Priority.HIGH
    
    def _process_request(self, request: SupportRequest) -> None:
        """Process complex technical support requests."""
        request.handled_by = self.handler_name
        request.resolution = "Escalated to senior engineers and provided expert-level solution"
        print(f"[{self.handler_name}] Handled: {request}")

class ManagerSupport(SupportHandler):
    """
    Manager level support handler - handles critical issues and escalations.
    """
    
    def __init__(self):
        super().__init__("Manager Support")
    
    def _can_handle(self, request: SupportRequest) -> bool:
        """Manager handles CRITICAL priority requests."""
        return request.priority == Priority.CRITICAL
    
    def _process_request(self, request: SupportRequest) -> None:
        """Process critical support requests requiring management attention."""
        request.handled_by = self.handler_name
        request.resolution = "Personally handled by management with immediate priority"
        print(f"[{self.handler_name}] Handled: {request}")

class SupportTicketSystem:
    """
    Client class that uses the Chain of Responsibility pattern
    to process support requests.
    """
    
    def __init__(self):
        # Create the chain of handlers
        self.level1 = Level1Support()
        self.level2 = Level2Support()
        self.level3 = Level3Support()
        self.manager = ManagerSupport()
        
        # Set up the chain: Level1 -> Level2 -> Level3 -> Manager
        self.level1.set_next(self.level2).set_next(self.level3).set_next(self.manager)
    
    def process_request(self, request: SupportRequest) -> None:
        """
        Process a support request through the chain of handlers.
        """
        print(f"\nProcessing: {request}")
        
        # Start the chain with the first handler
        if self.level1.handle_request(request):
            print(f"✓ Request resolved by: {request.handled_by}")
            print(f"  Resolution: {request.resolution}")
        else:
            print("✗ Request could not be handled by any support level")
    
    def generate_support_report(self, requests: list[SupportRequest]) -> None:
        """
        Generate a summary report of handled requests.
        """
        print("\n" + "="*60)
        print("SUPPORT TICKET SUMMARY REPORT")
        print("="*60)
        
        handler_stats: Dict[str, int] = {}
        
        for request in requests:
            if request.handled_by:
                handler_stats[request.handled_by] = handler_stats.get(request.handled_by, 0) + 1
        
        print("\nRequests handled by each support level:")
        for handler, count in handler_stats.items():
            print(f"  {handler}: {count} requests")
        
        print(f"\nTotal requests processed: {len(requests)}")
        print("="*60)

def main():
    """
    Demonstration of the Chain of Responsibility pattern
    with a customer support ticket system.
    """
    
    # Create the support system
    support_system = SupportTicketSystem()
    
    # Create various support requests with different priorities
    requests = [
        SupportRequest("REQ001", "John Doe", "Password reset needed", Priority.LOW),
        SupportRequest("REQ002", "Jane Smith", "Application crashes on startup", Priority.MEDIUM),
        SupportRequest("REQ003", "Bob Johnson", "Database connection issues", Priority.HIGH),
        SupportRequest("REQ004", "Alice Brown", "System-wide outage affecting all users", Priority.CRITICAL),
        SupportRequest("REQ005", "Charlie Wilson", "How to change profile picture", Priority.LOW),
        SupportRequest("REQ006", "Diana Ross", "API integration problems", Priority.MEDIUM),
    ]
    
    # Process each request through the chain
    print("PROCESSING SUPPORT REQUESTS")
    print("="*40)
    
    for request in requests:
        support_system.process_request(request)
    
    # Generate summary report
    support_system.generate_support_report(requests)
    
    # Demonstrate chain modification
    print("\n" + "="*60)
    print("DEMONSTRATING CHAIN MODIFICATION")
    print("="*60)
    
    # Create a new specialized handler for VIP customers
    class VIPSupport(SupportHandler):
        def __init__(self):
            super().__init__("VIP Support")
        
        def _can_handle(self, request: SupportRequest) -> bool:
            # VIP support handles requests from VIP customers (simplified check)
            return "VIP" in request.customer_name
        
        def _process_request(self, request: SupportRequest) -> None:
            request.handled_by = self.handler_name
            request.resolution = "Provided premium VIP support service"
            print(f"[{self.handler_name}] Handled: {request}")
    
    # Add VIP support at the beginning of the chain
    vip_support = VIPSupport()
    vip_support.set_next(support_system.level1)
    
    # Process a VIP request
    vip_request = SupportRequest("REQ007", "VIP Customer", "Need immediate assistance", Priority.LOW)
    print(f"\nProcessing VIP request: {vip_request}")
    
    if vip_support.handle_request(vip_request):
        print(f"✓ VIP Request resolved by: {vip_request.handled_by}")
    else:
        print("✗ VIP Request could not be handled")

if __name__ == "__main__":
    main()

Key Components in the Example

  • SupportRequest Class: Acts as the request object containing all necessary information including priority level, customer details, and issue description.
  • SupportHandler Abstract Base Class: Defines the common interface for all handlers with methods to set the next handler and process requests.
  • Concrete Handlers: Four different support levels (Level 1, Level 2, Level 3, and Manager) that each handle specific priority requests.
  • SupportTicketSystem Class: The client that sets up the chain and processes requests through it.

How the Chain Works

The example creates a chain where:

  • Level 1 Support handles LOW priority requests
  • Level 2 Support handles MEDIUM priority requests
  • Level 3 Support handles HIGH priority requests
  • Manager Support handles CRITICAL priority requests

When a request comes in, it starts at Level 1 Support. If that handler cannot process it, the request automatically moves to the next handler in the chain until it finds an appropriate handler or reaches the end.

Pattern Benefits Demonstrated

  • Flexibility: The example shows how easy it is to add a new VIP Support handler to the chain without modifying existing code.
  • Decoupling: The client (SupportTicketSystem) doesn’t need to know which specific handler will process each request.
  • Responsibility Distribution: Each handler has a clear, single responsibility based on request priority.
  • Dynamic Chain Modification: The chain can be reconfigured at runtime, as shown with the VIP Support addition.

Track your progress

Mark this subtopic as completed when you finish reading.