Chain Of Responsbility Pattern In C#/.NET Core

I had a friend who was taking a look through the classic “Gang Of Four” Design Patterns book for the first time. He reached out to ask me which of the design patterns I’ve actually used in business applications, and actually thought “I’m using this pattern right now”. Singleton, Factory Pattern, Mediator – I’ve used all of these and I’ve even written about them before. But one that I haven’t talked about before is the Chain Of Responsibility Pattern.

What Is “Chain Of Responsibility”

Chain Of Responsibility (Or sometimes I’ve called it Chain Of Command) pattern is a design pattern that allows “processing” of an object in hierarchical fashion. The classic Wikipedia definition is

In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain. Thus, the chain of responsibility is an object oriented version of the if … else if … else if ……. else … endif idiom, with the benefit that the condition–action blocks can be dynamically rearranged and reconfigured at runtime.

That probably doesn’t make much sense but let’s look at a real world example that we can then turn into code.

Let’s say I own a bank. Inside this bank I have 3 levels of employees. A Bank Teller, Supervisor, and a Bank Manager. If someone comes in to withdraw money, the Teller can allow any withdrawal of less than $10,000, no questions asked. If the amount is for more than $10,000, then it passes the request onto the supervisor. The supervisor can handle requests up to $100,000, but only if the account has ID on record. If the ID is not on record, then the request must be rejected no matter what. If the requested amount is for more than $100,000 it goes to the bank manager. The bank manager can approve any amount for withdrawal even if the ID is not on record because if they are withdrawing that amount, they are a VIP and we don’t care about ID and money laundering regulations.

This is the hierarchical “Chain” that we talked about earlier where each person tries to process the request, and can then pass it onto the next. If we take this approach and map it to code (In an elegant way), this is what we call the Chain Of Responsibility pattern. But before we go any further, let’s look at a bad way to solve this problem.

A Bad Approach

Let’s just solve this entire problem using If/Else statements.

class BankAccount
{
    bool idOnRecord { get; set; }

    void WithdrawMoney(decimal amount)
    {
        // Handled by the teller. 
        if(amount < 10000)
        {
            Console.WriteLine("Amount withdrawn by teller");
        } 
        // Handled by supervisor
        else if (amount < 100000)
        {
            if(!idOnRecord)
            {
                throw new Exception("Account holder does not have ID on record.");
            }

            Console.WriteLine("Amount withdrawn by Supervisor");
        }
        else
        {
            Console.WriteLine("Amount withdrawn by Bank Manager");
        }
    }
}

So there is a few issues with our code.

  • Adding additional levels of employees in here is really hard to manage with the mess of If/Else statements.
  • The special logic of checking ID at the supervisor level is somewhat hard to unit test because it has to pass a few other checks first.
  • While the only defining logic is for the amount withdrawn at the moment, we could add additional checks in the future (e.g. VIP customers are marked as such and are always handled by the supervisor). This logic is going to be hard to manage and could easily get out of control.

Coding Chain Of Responsibility

Let’s rewrite the code a little. Instead let’s create “employee” objects that can handle the logic of whether they can process the request themselves or not. Ontop of that, let’s give them a line manager so that they know they can pass the request up if needed.

interface IBankEmployee
{
    IBankEmployee LineManager { get; }
    void HandleWithdrawRequest(BankAccount account, decimal amount);
}

class Teller : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if(amount > 10000)
        {
            LineManager.HandleWithdrawRequest(account, amount);
            return;
        }

        Console.WriteLine("Amount withdrawn by Teller");
    }
}

class Supervisor : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if (amount > 100000)
        {
            LineManager.HandleWithdrawRequest(account, amount);
            return;
        }

        if(!account.idOnRecord)
        {
            throw new Exception("Account holder does not have ID on record.");
        }

        Console.WriteLine("Amount withdrawn by Supervisor");
    }
}

class BankManager : IBankEmployee
{
    public IBankEmployee LineManager { get; set; }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        Console.WriteLine("Amount withdrawn by Bank Manager");
    }
}

We can then create the “chain” by creating the employees required along with their managers. Almost like creating an Org Chart.

var bankManager = new BankManager();
var bankSupervisor = new Supervisor { LineManager = bankManager };
var frontLineStaff = new Teller { LineManager = bankSupervisor };

We can then completely transform the BankAccount class Withdraw method to instead be handled by our front line staff member (The Teller).

class BankAccount
{
    public bool idOnRecord { get; set; }

    public void WithdrawMoney(IBankEmployee frontLineStaff, decimal amount)
    {
            frontLineStaff.HandleWithdrawRequest(this, amount);
    }
}

Now, when we make a withdrawl request, the Teller always handles it first, if it can’t, it then passes it to it’s line manager *whoever* that may be. So the beauty of this pattern is

  • Subsequent items in the “chain” don’t need to know why things got passed to it. A supervisor doesn’t need to know what the requirements for on why a Teller passed it up the chain.
  • A Teller doesn’t need to know the entire chain after it. Just that it passed the request to the supervisor and it will be handled there (Or further if need be).
  • The entire org chart can be changed by introducing new employee types. For example if I created a “Teller Manager” that could handle requests between 10k -> 50k, and then pass it to the Supervisor. The Teller object would stay the same, The Supervisor object would stay the same, and I would just change the LineManager of the Teller to be the “Teller Manager” instead.
  • Any Unit Tests we write can focus on a single employee at once. For example when testing a Supervisor, we don’t also need to test the Teller’s logic on when it gets passed to it.

Extending Our Example

While I think the above example is a great way to illustrate the pattern, often you’ll find people using a method called “SetNext”. In general I think this is pretty uncommon in C# because we have property getters and setters. Using a “SetVariableName” method is typically from C++ (And for me – Pascal) days where that was the preferred way of encapsulating variables.

But ontop of that, other examples also typically use an Abstract Class to try and tighten how requests are passed along. The problem with our example above is that there is a lot of duplicate code of passing the request onto the next handler. Let’s tidy that up a little bit.

There is a lot of code so bare with me. The first thing we want to do is create an AbstractClass that allows us to handle the withdrawal request in a standardized way. It should check the condition, if it passes, do the withdraw, if not, it needs to pass it onto it’s line manager. That looks like so :

interface IBankEmployee
{
    IBankEmployee LineManager { get; }
    void HandleWithdrawRequest(BankAccount account, decimal amount);
}

abstract class BankEmployee : IBankEmployee
{
    public IBankEmployee LineManager { get; private set; }

    public void SetLineManager(IBankEmployee lineManager)
    {
        this.LineManager = lineManager;
    }

    public void HandleWithdrawRequest(BankAccount account, decimal amount)
    {
        if (CanHandleRequest(account, amount))
        {
            Withdraw(account, amount);
        } else
        {
            LineManager.HandleWithdrawRequest(account, amount);
        }
    }

    abstract protected bool CanHandleRequest(BankAccount account, decimal amount);

    abstract protected void Withdraw(BankAccount account, decimal amount);
}

Next we need to modify our employee classes to inherit from this BankEmployee class.

class Teller : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        if (amount > 10000)
        {
            return false;
        }
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        Console.WriteLine("Amount withdrawn by Teller");
    }
}

class Supervisor : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        if (amount > 100000)
        {
            return false;
        }
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        if (!account.idOnRecord)
        {
            throw new Exception("Account holder does not have ID on record.");
        }

        Console.WriteLine("Amount withdrawn by Supervisor");
    }
}

class BankManager : BankEmployee, IBankEmployee
{
    protected override bool CanHandleRequest(BankAccount account, decimal amount)
    {
        return true;
    }

    protected override void Withdraw(BankAccount account, decimal amount)
    {
        Console.WriteLine("Amount withdrawn by Bank Manager");
    }
}

So notice that in all cases, the public method of “HandleWithdrawRequest” from the abstract class is called, it then calls the subclass “CanHandleRequest” which can contain our logic on if this employee is good to go or not. If it is, then call it’s local “Withdraw” request, otherwise try the next employee.

We just need to change how we create the chain of employees like so :

var bankManager = new BankManager();

var bankSupervisor = new Supervisor();
bankSupervisor.SetLineManager(bankManager);

var frontLineStaff = new Teller();
frontLineStaff.SetLineManager(bankSupervisor);

Again, I prefer not to use the “SetX” methods, but it’s what a lot of examples use so I thought I would include it.

Other examples also put the logic of whether an employee can handle the request or not inside the actual abstract class. I personally prefer not to do this as it means all our handlers have to have very similar logic. So for example at the moment all are checking the amount to be withdrawn, but what if we had a particular handler that was looking for something in particular (Like a VIP flag?), adding that logic into the abstract class for some handlers but not others would just take us back to If/Else hell.

When To Use The “Chain Of Responsibility” Design Pattern?

The best use cases of this pattern are where you have a very logical “chain” of handlers that should be run in order every time. I would note that forking of the chain is a variation on this pattern, but quickly becomes extremely complex to handle. For that reason, I typically end up using this pattern when I am modelling real world “chain of command” scenarios. It’s the entire reason I use a bank as an example, because it’s a real world “Chain Of Responsibility” that can be modelled in code.

ENJOY THIS POST?
Join over 3.000 subscribers who are receiving our weekly post digest, a roundup of this weeks blog posts.
We hate spam. Your email address will not be sold or shared with anyone else.

3 comments

  1. That is a nice explanation. If you are testing the code then you should fix the NullReferenceException in HandleWithdrawRequest when someone tries to withdraw an amount to big for the current manager but there is no LineManager for that amount.

    1. Yeah I didn’t want to complicate things too much but you should definitely handle the case when there is no “next” handler.

Leave a Reply

Your email address will not be published. Required fields are marked *