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
Handlerclass defines the interface for handling requests and holds a reference to the next handler in the chain. - Concrete Handlers:
Manager,Director, andCEOclasses implement thehandle_requestmethod 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
SupportHandlerclass defines the interface for handling tickets and holds a reference to the next handler in the chain. - Concrete Handlers:
SupportAgent,Supervisor, andManagerclasses implement thehandle_ticketmethod 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
