The first design pattern in the Behavioral Category is the Chain of Responsibility Design Pattern. Let's take a look at this design pattern to see when and how to implement it in C# and .Net.
Note: Code can be downloaded at my Github.
1. What is the Chain of Responsibility Design Pattern?
Chain of Responsibility is a behavioral design pattern that lets you pass requests along a chain of handlers. Upon receiving a request, each handler decides either to process the request or to pass it to the next handler in the chain.
Chain of responsibility design pattern gives more than one object an opportunity to handle a request by linking receiving objects together in form of a chain.
2. When to implement the Chain of Responsibility Design Pattern?
In general, the Chain of Responsibility is used when:
- Your application is expected to process different requests in various ways, but you don't know the exact type of the request and the sequences are unknown beforehand
- It's essential to execute several handler in a particular order
- The set of handlers and their order are supposed to change at runtime
For example, you are building an online ordering system. The system requires the ordering process to be executed in sequence so that the order can be placed successfully. Your task is to building this system which is also easy to enhance in the future if there are requirements on adding steps into the existing sequence. The Chain of Responsibility Design Pattern is here to help you achieve this purpose.
3. How to implement the Chain of Responsibility Design Pattern?
We will build the online ordering system we mentioned in the example above. The sequence of the ordering process is like below:
User Authenticated > Cart Has Items > Address Is Provided > Payment Method Is Provided
1 2 3 4 5 6 7 8 | public class Order { public Guid ID { get ; set ; } = Guid.NewGuid(); public string Cart { get ; set ; } = null ; public string Address { get ; set ; } = null ; public string Payment { get ; set ; } = null ; public bool IsAuthenticated { get ; set ; } = false ; } |
Below are steps to implement the Chain of Responsibility Design Pattern:
Step 1: Create a Handler Interface
1 2 3 4 5 6 | public interface IHandler { IHandler SetNext(IHandler handler); string Handle(Order order); } |
Step 2: Create a Handler Abstract Class
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | abstract class AbstractHandler : IHandler { private IHandler _nextHandler; public IHandler SetNext(IHandler handler) { this ._nextHandler = handler; // Returning a handler from here will let us link handlers in a // convenient way like this: // monkey.SetNext(squirrel).SetNext(dog); return handler; } public virtual string Handle(Order order) { if ( this ._nextHandler != null ) { return this ._nextHandler.Handle(order); } else { return null ; } } } |
Step 3: Create Handler classes for each step in sequence
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | class AuthenticationHandler : AbstractHandler { public override string Handle(Order order) { if (!order.IsAuthenticated) { return $ "Authentication: The request is not valid." ; } else { Console.WriteLine($ " Authentication: Passed Authentication Handler." ); return base .Handle(order); } } } class CartHandler : AbstractHandler { public override string Handle(Order order) { if ( string .IsNullOrEmpty(order.Cart)) { return $ "Cart: The cart is empty." ; } else { Console.WriteLine($ " Cart: Passed Cart Handler." ); return base .Handle(order); } } } class AddressHandler : AbstractHandler { public override string Handle(Order order) { if ( string .IsNullOrEmpty(order.Address)) { return $ "Address: The address is not provided." ; } else { Console.WriteLine($ " Authentication: Passed Address Handler." ); return base .Handle(order); } } } class PaymentHandler : AbstractHandler { public override string Handle(Order order) { if ( string .IsNullOrEmpty(order.Payment)) { return $ "Payment: The payment method is not provided." ; } else { Console.WriteLine($ " Payment: Passed Payment Handler." ); return base .Handle(order); } } } |
Done! Now you can test the Chain of Responsibility Design Pattern in your application. Also, the sequence of steps can be changed based on your needs.
class Program { static void Main(string[] args) { List<Order> orders = new List<Order>() { new Order(), new Order() { IsAuthenticated = true }, new Order() { Cart = "Orange, Banana, Pork", IsAuthenticated = true }, new Order() { Cart = "Samsung Galaxy S9", IsAuthenticated = true, Address = "Brooklyn, NY" }, new Order() { Cart = "Macbook Pro", IsAuthenticated = true, Address = "Brooklyn, NY", Payment = "Credit Card" } }; var authentication = new AuthenticationHandler(); var cart = new CartHandler(); var address = new AddressHandler(); var payment = new PaymentHandler(); authentication.SetNext(cart).SetNext(address).SetNext(payment); Client.ClientCode(authentication, orders); Console.WriteLine(); } } class Client { // The client code is usually suited to work with a single handler. In // most cases, it is not even aware that the handler is part of a chain. public static void ClientCode(AbstractHandler handler, List<Order> orders) { foreach (var o in orders) { Console.WriteLine($"Client: Processing Order ID: {o.ID}"); var result = handler.Handle(o); if (result != null) { Console.Write($" {result}"); } else { Console.WriteLine($" Order {o.ID} is processed successfully!"); } Console.WriteLine($"\n"); } } }
Output:
4. Conclusion
With the help of the Chain of Responsibility Design Pattern, your application can be ensured to run in process that you prefer. I hope this article helps you to build a better application. Please let me know your thoughts by commenting below.