Chain of Responsibility Pattern

Greetings, fellow developers! Welcome back to .Net Core Central. In today’s blog post, we’re diving into another fascinating design pattern – the Chain of Responsibility.

This pattern, belonging to the Gang of Four design patterns, falls under the category of behavioral design patterns. Its primary aim is to decouple the sender of a request from its receiver, allowing multiple objects the opportunity to handle the request.

Understanding the Chain of Responsibility

In the realm of behavioral design patterns, such as the State and Observer patterns, the focus is on object composition over inheritance. The Chain of Responsibility design pattern aligns with this philosophy.

Rather than having a single class handle a request, the pattern divides the responsibility among multiple handlers. These handlers, based on the situation, decide whether to handle the request or pass it to the next level in the chain.

Real-world Example: Expense Report Approval

Let’s illustrate the Chain of Responsibility with a practical example – an expense report approval scenario. We’ll have three levels of approval: a manager, a vice president, and a CEO. Each level of approval corresponds to a handler in our chain. When an expense report is created, it navigates through these three levels of approval, creating a chain of responsibility.

Chain of Responsibility Implementation in .NET Core

Let’s take a closer look at the implementation. We’ll start with the SeniorManager class, representing the first level of approval. The class implements the IManager interface, defining methods for approving requests and setting the supervisor for fallback approval.

namespace ChainOfResponsibility.Pattern;

internal record ExpenseReport(string Name, int Amount);

internal interface IManager
{
    void SetSupervisor(IManager manager);
    void ApproveRequest(ExpenseReport expenseReport);
}

internal class SeniorManager : IManager
{
    private IManager _manager;

    public void ApproveRequest(ExpenseReport expenseReport)
    {
        if (expenseReport.Amount < 500)
            Console.WriteLine("Approved by Manager");
        else
            _manager?.ApproveRequest(expenseReport);
    }

    public void SetSupervisor(IManager manager)
    {
        _manager = manager;
    }
}

Next, we have the VicePresident class and the COO class, both implementing the IManager interface. Each class sets its supervisor and specifies the amount it can approve. The logic in these classes reflects the hierarchical responsibility structure.

internal class VicePresident : IManager
{
    private IManager _manager;

    public void ApproveRequest(ExpenseReport expenseReport)
    {
        if (expenseReport.Amount < 1000)
            Console.WriteLine("Approved by VP");
        else
            _manager?.ApproveRequest(expenseReport);
    }

    public void SetSupervisor(IManager manager)
    {
        _manager = manager;
    }
}

internal class COO : IManager
{
    private IManager _manager;

    public void ApproveRequest(ExpenseReport expenseReport)
    {
        if (expenseReport.Amount < 5000)
            Console.WriteLine("Approved by COO");
        else
            Console.WriteLine("Not approved");
    }

    public void SetSupervisor(IManager manager)
    {
        _manager = manager;
    }
}

Putting it All Together

To tie everything together, we instantiate the three classes – SeniorManager, VicePresident, and CEO. We set the supervisors accordingly, creating the chain of responsibility. Then, we create an expense report for various scenarios – a monitor purchase, a desk purchase, and a travel expense.

using ChainOfResponsibility.Pattern;

var manager = new SeniorManager();
var vp = new VicePresident();
var coo = new COO();

manager.SetSupervisor(vp);
vp.SetSupervisor(coo);

var expense = new ExpenseReport("Monitor", 100);
Console.WriteLine(expense);
manager.ApproveRequest(expense);
Console.WriteLine("----------------------------");

expense = new ExpenseReport("Desk", 900);
Console.WriteLine(expense);
manager.ApproveRequest(expense);
Console.WriteLine("----------------------------");

expense = new ExpenseReport("Travel", 5500);
Console.WriteLine(expense);
manager.ApproveRequest(expense);
Console.WriteLine("----------------------------");

Running the Application

Upon running the application, we observe the chain in action. The console output shows which handler approves or declines each expense based on the defined criteria.

chain of responsibility pattern

Conclusion

In essence, the Chain of Responsibility design pattern provides an elegant solution for handling hierarchical responsibilities. It aligns with SOLID principles, particularly the Single Responsibility Principle while introducing a flexible and extensible way to manage the flow of requests.

That wraps up today’s exploration of the Chain of Responsibility design pattern. If you found this blog post helpful, don’t forget to visit the rest of the blog posts.

For more content like this, consider visiting and subscribing to my YouTube channel .Net Core Central. Thank you for joining me in unraveling the intricacies of design patterns in .NET Core!