Design Patterns - Chain of Responsibility
In our previous blogs on design pattern series, we covered various structural design patterns. Today, we will move on to the next category of design patterns called Behavioral Design Pattern. Under this category, we will particularly focus on the Chain of Responsibility pattern.
Table of contents
- Introduction to Behavioral Design Pattern
- Understanding Chain of responsibility design pattern
- Conclusion
Introduction to Behavioral Design Pattern
As you already know, Creational and Structural Design Patterns deal with -
- How objects can be created
- How classes and objects can be assembled effectively.
We still need to figure out how objects can communicate flexibly with each other through a loosely coupled code. Also, when the behavior or the functionality of the class gets modified, the class and objects that interact with the modified class should not get impacted. Behavioral Design Pattern addresses all these issues.
Let us now understand more about the Chain of Responsibility Design Pattern, which is an important Behavioral Design Pattern.
Understanding Chain of responsibility design pattern
To understand this better, imagine a scenario where you need to accomplish a task for which approval is required. Now, the important thing is that the approver has certain restrictions based on the approval request. Depending on the request, the approver also changes.
For example, imagine a Multi-National Company that has a leave application portal.
- If any employee applies for leave for about 1-3 days, then the approval can be done by the employee’s immediate manager.
- However, if the number of days is more than 3 but less than 10 days, then the employee’s skip-level manager has to approve the request.
- If it is beyond 10 days, then the request has to go to the unit head.
So here, based on the number of days of leave requested by the employee, the approver changes. Since the number of days is provided by the employee during the run time of the application, the approver also gets decided at the run time.
To handle such situations, the chain of responsibility design pattern provides an efficient solution. Here is the template that we will use to understand the solution better.
The Chain of responsibility design pattern consists of the following components.
- Client
- Handler
- Concrete Handler
using System;
namespace PARTECH_ChainOfResponsibility
{
internal class Program
{
static void Main(string[] args)
{
var handlerA = new ConcreteHandlerA();
var handlerB = new ConcreteHandlerB();
handlerA.nextHandler(handlerB);
handlerA.HandleTask(0);
handlerA.HandleTask(1);
Console.ReadLine();
}
}
public abstract class Handler
{
public Handler handler;
public void nextHandler(Handler handler)
{
this.handler = handler;
}
public abstract void HandleTask(int request);
}
public class ConcreteHandlerA: Handler
{
public override void HandleTask(int request)
{
if (request == 0)
{
Console.WriteLine("Request is at Concrete Handler A");
}
else if (handler != null)
{
handler.HandleTask(request);
}
}
}
public class ConcreteHandlerB : Handler
{
public override void HandleTask(int request)
{
if (request == 1)
{
Console.WriteLine("Request is at Concrete Handler B");
}
else if (handler != null)
{
handler.HandleTask(request);
}
}
}
}
Here, The handler is the abstract class that has the code to assign the next handler. It has the property to hold the object of the next handler and also has the abstract method to handle the task.
ConcreteHandlerA and ConcreteHandlerB are two classes that implement the HandleTask method in the abstract class Handler.
The main method is the client here, which has the code to create two objects - one for ConcreteHandlerA and the other for ConcreteHandlerB. And it also has the code to assign ConcreteHandlerB’s object as the next handler to ConcreteHandlerA.
On executing the above code, the below output gets printed.
Now that we have understood the template to implement the Chain of responsibility pattern, let us consider another practical scenario and implement the same.
Imagine a bank that has digitized its operation completely. Whenever a customer comes in person to withdraw money, for safety purposes, the bank has implemented an approval system based on the amount withdrawn.
- For an amount less than 10000, the cashier approves
- For an amount between 10001 to 25000, the manager approves
- Anything above that, the senior manager approves.
Let us implement this functionality using the chain of responsibility pattern.
using System;
namespace PARTECH_ChainOfResponsibility
{
internal class Program
{
static void Main(string[] args)
{
var cashier = new Cashier();
var manager = new Manager();
var seniorManager = new SeniorManager();
cashier.AddtionalApprover(manager);
manager.AddtionalApprover(seniorManager);
cashier.ApproveAmount(5000);
cashier.ApproveAmount(12500);
cashier.ApproveAmount(40000);
Console.ReadLine();
}
}
public abstract class Approver
{
public Approver approver;
public void AddtionalApprover(Approver approver)
{
this.approver = approver;
}
public abstract void ApproveAmount(double amount);
}
public class Cashier: Approver
{
public override void ApproveAmount(double amount)
{
if (amount <= 10000)
{
Console.WriteLine($"Amount of {amount} can be approved by the Cashier.");
}
else
{
if (approver != null)
{
approver.ApproveAmount(amount);
}
}
}
}
public class Manager : Approver
{
public override void ApproveAmount(double amount)
{
if (amount <= 25000)
{
Console.WriteLine($"Amount of {amount} can be approved by the Manager.");
}
else
{
if (approver != null)
{
approver.ApproveAmount(amount);
}
}
}
}
public class SeniorManager : Approver
{
public override void ApproveAmount(double amount)
{
Console.WriteLine($"Amount of {amount} can be approved by the Senior Manager.");
}
}
}
The above code provides the desired solution by implementing the chain of responsibility pattern. The approver is the Handler abstract class here. Approver class has an abstract method ApproveAmount and has a method to assign AdditionalApprover to the property in the class. Cashier, Manager, and Senior Manager are the concrete handler classes that implement the approver abstract class. They have their respective logic inside the ApproveAmount. Up to 10000 for Cashier, up to 25000 for the manager, and maximum limit for the senior manager.
The main method is the client, which has the code to create three objects for Cashier, Manager, and Senior Manager. For Cashier, the Manager is assigned the immediate approver. For the Manager, the Senior Manager is assigned the immediate approver. And the Approveamount in the cashier class is called using three different amounts. 5000, 12500, and 40000.
On running the above code, the below output gets printed in the console window.
Conclusion
And that’s what a Chain of Responsibility Design Pattern is all about. In this post, we have also seen how to implement it in a real-world scenario. Stay tuned for the next blog on the Design Pattern series.