Deep Dive into Behavioral Patterns - The Chain of Responsibility Pattern.
Hey software designers! Today, we’re diving into the Chain of Responsibility pattern. This pattern is essential for creating a chain of processing objects where each object in the chain handles a request or passes it to the next object in the chain. Let’s explore its workings, benefits, and real-world applications with detailed examples.
What is the Chain of Responsibility Pattern?
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. It decouples the sender of the request from its receiver by allowing multiple objects to handle the request without the sender knowing which object will handle it.
Real-World Scenario
Imagine you’re developing a help desk ticketing system where different levels of support handle different types of issues. A customer service representative handles basic issues, a technical support representative handles more complex issues, and a manager handles the most critical issues. Each issue needs to be escalated to the appropriate level of support based on its complexity.
The Problem
When processing requests that can be handled by multiple handlers, hardcoding the logic to determine which handler should process each request can lead to a tightly coupled and inflexible codebase. This approach makes it difficult to add or modify handlers without changing the existing code.
Without Chain of Responsibility Pattern
class HelpDesk
def handle_request(issue)
if issue.type == 'basic'
handle_basic_issue(issue)
elsif issue.type == 'technical'
handle_technical_issue(issue)
elsif issue.type == 'critical'
handle_critical_issue(issue)
else
puts 'Unknown issue type'
end
end
def handle_basic_issue(issue)
puts "Handling basic issue: #{issue.description}"
end
def handle_technical_issue(issue)
puts "Handling technical issue: #{issue.description}"
end
def handle_critical_issue(issue)
puts "Handling critical issue: #{issue.description}"
end
end
issue1 = OpenStruct.new(type: 'basic', description: 'Password reset')
issue2 = OpenStruct.new(type: 'technical', description: 'Software installation')
issue3 = OpenStruct.new(type: 'critical', description: 'System outage')
help_desk = HelpDesk.new
help_desk.handle_request(issue1)
help_desk.handle_request(issue2)
help_desk.handle_request(issue3)
Drawbacks: The code is tightly coupled to specific implementations and hard to extend to support new issue types.
The Solution: Chain of Responsibility Pattern
Using the Chain of Responsibility pattern, we can create a chain of handlers that process the request or pass it to the next handler in the chain, promoting flexibility and scalability.
With Chain of Responsibility Pattern
Step 1: Define the Handler Interface
class Handler
def initialize(successor = nil)
@successor = successor
end
def handle_request(issue)
if @successor
@successor.handle_request(issue)
else
puts 'Request not handled'
end
end
end
Step 2: Create Concrete Handlers
class CustomerServiceHandler < Handler
def handle_request(issue)
if issue.type == 'basic'
puts "Handling basic issue: #{issue.description}"
else
super(issue)
end
end
end
class TechnicalSupportHandler < Handler
def handle_request(issue)
if issue.type == 'technical'
puts "Handling technical issue: #{issue.description}"
else
super(issue)
end
end
end
class ManagerHandler < Handler
def handle_request(issue)
if issue.type == 'critical'
puts "Handling critical issue: #{issue.description}"
else
super(issue)
end
end
end
Step 3: Implement Client Code
manager = ManagerHandler.new
technical_support = TechnicalSupportHandler.new(manager)
customer_service = CustomerServiceHandler.new(technical_support)
issue1 = OpenStruct.new(type: 'basic', description: 'Password reset')
issue2 = OpenStruct.new(type: 'technical', description: 'Software installation')
issue3 = OpenStruct.new(type: 'critical', description: 'System outage')
customer_service.handle_request(issue1)
customer_service.handle_request(issue2)
customer_service.handle_request(issue3)
Benefits: Decouples the sender of the request from its receiver, promoting flexibility and scalability.
Real-World Benefits
Scenario: Adding New Issue Types
Imagine you need to add a new issue type (e.g., ‘billing’). Using the Chain of Responsibility pattern, you can easily introduce a new handler for billing issues without modifying the existing code.
Without Chain of Responsibility Pattern:
class HelpDesk
def handle_request(issue)
if issue.type == 'basic'
handle_basic_issue(issue)
elsif issue.type == 'technical'
handle_technical_issue(issue)
elsif issue.type == 'critical'
handle_critical_issue(issue)
elsif issue.type == 'billing'
handle_billing_issue(issue)
else
puts 'Unknown issue type'
end
end
def handle_basic_issue(issue)
puts "Handling basic issue: #{issue.description}"
end
def handle_technical_issue(issue)
puts "Handling technical issue: #{issue.description}"
end
def handle_critical_issue(issue)
puts "Handling critical issue: #{issue.description}"
end
def handle_billing_issue(issue)
puts "Handling billing issue: #{issue.description}"
end
end
Drawbacks: Tightly coupled code that is difficult to maintain and extend.
With Chain of Responsibility Pattern:
class BillingHandler < Handler
def handle_request(issue)
if issue.type == 'billing'
puts "Handling billing issue: #{issue.description}"
else
super(issue)
end
end
end
billing = BillingHandler.new(manager)
technical_support = TechnicalSupportHandler.new(billing)
customer_service = CustomerServiceHandler.new(technical_support)
issue4 = OpenStruct.new(type: 'billing', description: 'Invoice discrepancy')
customer_service.handle_request(issue4)
Benefits: Clean, maintainable code with high flexibility and extensibility.
Conclusion
The Chain of Responsibility pattern is a powerful tool for creating a chain of processing objects where each object in the chain handles a request or passes it to the next object in the chain. It promotes flexibility, scalability, and maintainability in your code. By using the Chain of Responsibility pattern, you can easily manage requests that can be handled by multiple handlers without tightly coupling the sender to the receivers. Incorporate the Chain of Responsibility pattern into your design strategies to build more robust and adaptable software systems.
Stay tuned for more insights into software design principles and patterns.
Thôi Lo Code Đi Kẻo Sếp nạt!