Introduction

The Chain of Responsibility pattern is a behavioral design pattern that allows an object to pass a request along a chain of potential handlers until the request is handled. This pattern decouples the sender of a request from its receiver by giving multiple objects a chance to handle the request.

Key Concepts

  • Handler: An interface or abstract class that defines a method for handling requests.
  • Concrete Handler: A class that implements the handler interface and processes requests it is responsible for.
  • Client: The object that initiates the request.

Advantages

  • Decoupling: The sender and receiver are decoupled, allowing for more flexible and reusable code.
  • Responsibility Sharing: Multiple objects can handle the request, distributing the responsibility.
  • Dynamic Changes: The chain can be modified dynamically at runtime.

Disadvantages

  • Uncertain Handling: There is no guarantee that a request will be handled.
  • Complexity: The chain can become complex and difficult to manage.

Structure

The Chain of Responsibility pattern typically involves the following components:

  1. Handler Interface: Defines the method for handling requests.
  2. Concrete Handlers: Implement the handler interface and handle specific types of requests.
  3. Client: Sends requests to the chain.

UML Diagram

Client --> Handler --> ConcreteHandler1 --> ConcreteHandler2 --> ConcreteHandler3

Example

Let's consider an example where we have a chain of handlers to process a purchase request. The handlers are responsible for approving the request based on the amount.

Step-by-Step Implementation

  1. Define the Handler Interface
class Handler:
    def __init__(self, successor=None):
        self._successor = successor

    def handle_request(self, request):
        raise NotImplementedError("This method should be overridden by subclasses")
  1. Create Concrete Handlers
class Manager(Handler):
    def handle_request(self, request):
        if request.amount < 1000:
            print(f"Manager approved the request for {request.amount}")
        elif self._successor:
            self._successor.handle_request(request)

class Director(Handler):
    def handle_request(self, request):
        if request.amount < 10000:
            print(f"Director approved the request for {request.amount}")
        elif self._successor:
            self._successor.handle_request(request)

class CEO(Handler):
    def handle_request(self, request):
        if request.amount >= 10000:
            print(f"CEO approved the request for {request.amount}")
        elif self._successor:
            self._successor.handle_request(request)
  1. Create the Request Class
class Request:
    def __init__(self, amount):
        self.amount = amount
  1. Client Code
# Create the chain of handlers
manager = Manager()
director = Director(manager)
ceo = CEO(director)

# Create requests
requests = [Request(500), Request(5000), Request(15000)]

# Process each request
for req in requests:
    ceo.handle_request(req)

Explanation

  • Handler Interface: The Handler class defines the interface for handling requests and holds a reference to the next handler in the chain.
  • Concrete Handlers: Manager, Director, and CEO classes implement the handle_request method to process requests based on the amount.
  • Client Code: The client creates a chain of handlers and sends requests to the chain.

Practical Exercise

Exercise

Create a chain of responsibility for handling customer support tickets. The chain should include the following handlers:

  1. Support Agent: Handles tickets with priority less than 3.
  2. Supervisor: Handles tickets with priority less than 5.
  3. Manager: Handles tickets with priority 5 and above.

Solution

  1. Define the Handler Interface
class SupportHandler:
    def __init__(self, successor=None):
        self._successor = successor

    def handle_ticket(self, ticket):
        raise NotImplementedError("This method should be overridden by subclasses")
  1. Create Concrete Handlers
class SupportAgent(SupportHandler):
    def handle_ticket(self, ticket):
        if ticket.priority < 3:
            print(f"Support Agent handled ticket with priority {ticket.priority}")
        elif self._successor:
            self._successor.handle_ticket(ticket)

class Supervisor(SupportHandler):
    def handle_ticket(self, ticket):
        if ticket.priority < 5:
            print(f"Supervisor handled ticket with priority {ticket.priority}")
        elif self._successor:
            self._successor.handle_ticket(ticket)

class Manager(SupportHandler):
    def handle_ticket(self, ticket):
        if ticket.priority >= 5:
            print(f"Manager handled ticket with priority {ticket.priority}")
        elif self._successor:
            self._successor.handle_ticket(ticket)
  1. Create the Ticket Class
class Ticket:
    def __init__(self, priority):
        self.priority = priority
  1. Client Code
# Create the chain of handlers
agent = SupportAgent()
supervisor = Supervisor(agent)
manager = Manager(supervisor)

# Create tickets
tickets = [Ticket(1), Ticket(4), Ticket(5)]

# Process each ticket
for ticket in tickets:
    manager.handle_ticket(ticket)

Explanation

  • Handler Interface: The SupportHandler class defines the interface for handling tickets and holds a reference to the next handler in the chain.
  • Concrete Handlers: SupportAgent, Supervisor, and Manager classes implement the handle_ticket method to process tickets based on priority.
  • Client Code: The client creates a chain of handlers and sends tickets to the chain.

Common Mistakes and Tips

  • Not Setting Successor: Ensure that each handler in the chain has a reference to the next handler.
  • Handling All Requests: Make sure that each handler only processes requests it is responsible for and passes others down the chain.
  • Circular Chains: Avoid creating circular chains where a handler might end up passing a request back to itself.

Conclusion

The Chain of Responsibility pattern is a powerful tool for decoupling the sender and receiver of a request, allowing multiple objects to handle the request in a flexible and dynamic manner. By understanding and implementing this pattern, you can create more maintainable and scalable software systems.

© Copyright 2024. All rights reserved