Nov 25 2024
Sponsored
⢠JetBrains Rider is now FREE for non-commercial development, making it more accessible for hobbyists, students, content creators, and open source contributors.
⢠Tired of outdated API documentation holding your team back? Postman simplifies your life by automatically syncing documentation with your API updates - no more static docs, no more guesswork! Read more.
Many thanks to the sponsors who make it possible for this newsletter to be free for readers. Become a sponsor.
The Chain of Responsibility pattern is a behavioral design pattern that allows you to build a chain of objects to handle a request or perform a task. Each object in the chain has the ability to either handle the request or pass it on to the next object in the chain. This pattern promotes loose coupling and flexibility in handling requests. Let's explore how to implement the Chain of Responsibility pattern in .NET 6 using a practical example.
Let's consider a scenario where we have a series of discount rules for an e-commerce application. Depending on the customer's profile, we want to apply different discount percentages to their orders. The discount rules are as follows: ⢠If the customer is a VIP, apply a 20% discount. ⢠If the customer is a regular customer, apply a 10% discount. ⢠If the customer is a new customer, apply a 5% discount. ⢠If none of the above rules match, apply no discount. Initially, we can handle this logic using a series of If statements:
public decimal CalculateDiscount(Customer customer, decimal orderTotal){ if (customer.IsVIP) { return orderTotal * 0.8m; // 20% discount } else if (customer.IsRegular) { return orderTotal * 0.9m; // 10% discount } else if (customer.IsNew) { return orderTotal * 0.95m; // 5% discount } else { return orderTotal; // no discount }}
While the If statements approach works, it can become unwieldy when the number of rules grows. The Chain of Responsibility pattern provides a more flexible and maintainable solution. Let's refactor the code to use this pattern. Step #1: Create an abstract handler class, DiscountHandler, that defines a common interface for all discount handlers:
public abstract class DiscountHandler{ protected DiscountHandler _nextHandler;Ā public void SetNextHandler(DiscountHandler nextHandler) { _nextHandler = nextHandler; }Ā public abstract decimal CalculateDiscount(Customer customer, decimal orderTotal);}
Step #2: Implement concrete discount handlers by deriving from DiscountHandler. Each handler will handle a specific rule and decide whether to apply a discount or pass the request to the next handler. VIPDiscountHandler:
public class VIPDiscountHandler : DiscountHandler{ public override decimal CalculateDiscount(Customer customer, decimal orderTotal) { if (customer.IsVIP) { return orderTotal * 0.8m; // 20% discount }Ā return _nextHandler?.CalculateDiscount(customer, orderTotal) ?? orderTotal; }}
RegularDiscountHandler:
public class RegularDiscountHandler : DiscountHandler{ public override decimal CalculateDiscount(Customer customer, decimal orderTotal) { if (customer.IsRegular) { return orderTotal * 0.9m; // 10% discount }Ā return _nextHandler?.CalculateDiscount(customer, orderTotal) ?? orderTotal; }}
NewCustomerDiscountHandler:
public class NewCustomerDiscountHandler : DiscountHandler{ public override decimal CalculateDiscount(Customer customer, decimal orderTotal) { if (customer.IsNew) { return orderTotal * 0.95m; // 5% discount }Ā return _nextHandler?.CalculateDiscount(customer, orderTotal) ?? orderTotal; }}
NoDiscountHandler:
public class NoDiscountHandler : DiscountHandler{ public override decimal CalculateDiscount(Customer customer, decimal orderTotal) { return orderTotal; // no discount }}
Step #3: With the concrete handlers in place, we can create the chain by linking them together:
var vipHandler = new VIPDiscountHandler();Ā vipHandler.SetNextHandler(new RegularDiscountHandler()) .SetNextHandler(new NewCustomerDiscountHandler()) .SetNextHandler(new NoDiscountHandler());
Finally, we can invoke the chain by calling the CalculateDiscount method on the first handler in the chain:
decimal discountAmount = vipHandler.CalculateDiscount(customer, orderTotal);
What are the benefits from this?
Flexibility The Chain of Responsibility pattern allows you to dynamically modify or extend the chain without affecting other parts of the code. You can add or remove handlers as needed.
Loose coupling The pattern promotes loose coupling between the sender of a request and its receivers. Each handler only needs to know about its immediate successor, minimizing dependencies.
Single Responsibility Principle You can decouple classes that invoke operations from classes that perform operations.
Request may go unhandled If none of the handlers in the chain can handle the request, it may go unhandled, leading to unexpected behavior. It's important to have a default handler or a way to handle such scenarios.
Potential performance impact If the chain becomes very long, it may result in performance overhead due to the traversal of multiple handlers.
Remember, it's essential to strike a balance between the number of handlers and performance considerations when applying this pattern to real-world scenarios.
That's all from me for today.
For more design patterns, check out the Strategy Pattern in .NET and the Adapter Pattern in .NET.
1. Design Patterns that Deliver
This isnāt just another design patterns book. Dive into real-world examples and practical solutions to real problems in real applications.Check out it here.
Go-to resource for understanding the core concepts of design patterns without the overwhelming complexity. In this concise and affordable ebook, I've distilled the essence of design patterns into an easy-to-digest format. It is a Beginner level. Check out it here.
Every Monday morning, I share 1 actionable tip on C#, .NET & Arcitecture topic, that you can use right away.
Promote yourself to 20,000+ subscribers by sponsoring this newsletter.
Join 20,000+ subscribers to improve your .NET Knowledge.
Subscribe to the TheCodeMan.net and be among the 20,000+ subscribers gaining practical tips and resources to enhance your .NET expertise.