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:
- Handler Interface: Defines the method for handling requests.
- Concrete Handlers: Implement the handler interface and handle specific types of requests.
- Client: Sends requests to the chain.
UML Diagram
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
- 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")
- 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)
- Create the Request Class
- 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
, andCEO
classes implement thehandle_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:
- Support Agent: Handles tickets with priority less than 3.
- Supervisor: Handles tickets with priority less than 5.
- Manager: Handles tickets with priority 5 and above.
Solution
- 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")
- 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)
- Create the Ticket Class
- 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
, andManager
classes implement thehandle_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.
Software Design Patterns Course
Module 1: Introduction to Design Patterns
- What are Design Patterns?
- History and Origin of Design Patterns
- Classification of Design Patterns
- Advantages and Disadvantages of Using Design Patterns
Module 2: Creational Patterns
Module 3: Structural Patterns
Module 4: Behavioral Patterns
- Introduction to Behavioral Patterns
- Chain of Responsibility
- Command
- Interpreter
- Iterator
- Mediator
- Memento
- Observer
- State
- Strategy
- Template Method
- Visitor
Module 5: Application of Design Patterns
- How to Select the Right Pattern
- Practical Examples of Pattern Usage
- Design Patterns in Real Projects
- Refactoring Using Design Patterns
Module 6: Advanced Design Patterns
- Design Patterns in Modern Architectures
- Design Patterns in Microservices
- Design Patterns in Distributed Systems
- Design Patterns in Agile Development