Learn the Chain of Responsibility Design Pattern in .NET C# with enhancements.
Chain of Responsibility Design Pattern Definition
The Chain of Responsibility Design Pattern is one of the behavioral design patterns.
It allows passing a request or an object through a chain of handlers.
Once a handler receives a request, it decides whether it can process the request or pass it to the next handler in the chain.
If you already have some experience with Web Development, most probably you heard about something called Middleware. The whole system of handling Middleware parts is using the Chain of Responsibility Design Pattern.
Chain of Responsibility Design Pattern Advantages
Ok, assume that you have a system where you need to apply a series of steps on an object or an incoming request. You might think of applying all these steps into a single module like having a huge method which runs the object or the request through all the steps one by one and finally returns a result.
Yes, it would work, however, you know that in software it is not only about having working code, it is also about having a maintainable and extensible code.
If you follow this approach you would end up with a huge method or module which is doing too many things. Additionally, every time you need to extend the steps you will have to revisit this module or method and apply further modifications. All of this would eventually end up with a fragile system.
However, if you implement the Chain of Responsibility Design Pattern, you would end up with a modular system where you have control over each module which already does as minimum as possible.
Someone might argue that even if we split these steps into smaller modules, the main module would eventually use and run all these smaller modules, so at the end it would be the same.
Actually, no. Yes, the main module would use and run all the smaller modules as at the end this is the business we need to achieve, however, it is not about if some logic is executed or not, it is about which module is responsible for executing which logic.
Having the whole big business rules and logic split into smaller modules helps having control, being able to easily maintain, extend, test,…
Important Note
I have some concerns about the Chain of Responsibility Design Pattern which I would share with you later in this article. However, first let’s cover the basics.
How does the Chain of Responsibility Design Pattern work?
Let’s assume that you want your request to be processed by handlers in sequence but if any of them fails, you want to stop the chain and get an immediate descriptive response.
In this case, the diagram would be as follows:
And the process would be as follows:
The request is passed to the first handler.
The handler validates if it is able to process the request or not. If yes, it processes the request and passes it to the next handler. If not, it fails with some descriptive message or response to the caller.
However, this is not the only way of doing it. May be your requirements are a bit different that you only need to have a response after the first successful handler whatever its order in the chain is.
In this case, the diagram would be as follows:
And the process would be as follows:
The request is passed to the first handler.
The handler validates if it is able to process the request or not. If yes, it processes the request and returns to the caller with a successful response. If not, it passes the request to the next hander.
However, even when having different ways of working, we would have the same class diagram as the differences would be in the implementations, not the abstractions.
Having that said, let’s now move on to implementing the Chain of Responsibility Design Pattern using a simple example.
Implementing The Chain of Responsibility Design Pattern
On this example, we are not going to bother ourselves with the business to implement.
We are just going to assume that we have a request which passes through a series of handlers so that finally we have one response.
To be able to check if our implementation works fine, we are going to use a list of strings which would be extended by every handler.
Therefore, we are going to create a new Solution called “ChainOfResponsibility” with a single Console Application Project called “ChainOfResponsibilityImplementation”.
If you follow the steps as in the next sections in this article, you should end up with this:
IHandler
What we can notice here:
It is a simple interface representing any handler.
We defined void SetNextHandler(IHandler next) to set the next handler in the chain.
We defined IResponse Handle(IRequest request) to actually handle a request.
IRequest
What we can notice here:
It is a simple interface representing any request.
We defined int Id { get; }.
We defined IReadOnlyCollection<string> Messages { get; } to act as the list of strings to be used for validating our solution.
IResponse
What we can notice here:
It is a simple interface representing any response.
We defined IReadOnlyCollection<string> Messages { get; } to act as the list of strings to be used for validating our solution.
Request
What we can notice here:
This is a simple implementation of IRequest.
It is immutable.
Response
What we can notice here:
This is a simple implementation of IResponse.
It is immutable.
HandlerBase
What we can notice here:
It is implementing IHandler.
We defined protected IHandler NextHandler to represent the next handler in the chain.
Handler1
What we can notice here:
It is extending HandlerBase.
It prepares the new messages list by copying the messages list in the incoming request and extends it by the message “Handled by Handler 1”.
If a next handler is set, it calls its Handle method passing in a newly created request with the new messages list.
Otherwise, it just returns a response.
Note that we didn’t add here any validations or handle failing cases for simplicity. However, in the real world, this should have been taken care of.
Handler2
Same as Handler1 with replacing the message “Handled by Handler 1” with “Handled by Handler 2”.
AggregateHandler
What we can notice here:
This is a wrapper handler which would be composed of all the handlers and then be responsible for setting the chain by properly calling the SetNextHandler method of every handler.
This is better than leaving or delegating this job to the main module.
It is also extending HandlerBase so that it could be used as a standard handler.
Program
What we can notice here:
We created an initial request.
We created an AggregateHandler passing in Handler1 and Handler2 in this order.
Then writing the concatenated messages list to the console.
Then, we created an AggregateHandler passing in Handler2 and Handler1 in this order.
Then writing the concatenated messages list to the console.
Running this, you would end up with this result:
Great, it is working as it should.
My Concern about Chain of Responsibility Design Pattern
If you remember, I already told you on a previous section in this article that I have a concern. My concern is that following this design, we have more than one problem.
From where I see it, we would have the following problems:
Handlers are doing too many things.
Handlers are not only providing their own implementations, but also controlling the whole sequence.
Every Handler could decide to do things in a different way.
The main module is totally ignorant about what happens within hops between handlers.
What if a certain handler decide to just not call the next handler? may be due to a bug in coding? or something fishy?
Additionally, let’s imagine that the handlers are actually third party plugins that the end user can add and remove as he wishes. In this case, leaving the full control in the plugins’ hands would not be good.
Therefore, I would actually prefer to apply some modifications on the design to address these problems.
However, I am not sure if after applying these modifications we can still call it a Chain of Responsibility Design Pattern 🤷♂️
Better Implementation of The Chain of Responsibility Design Pattern
On this implementation, mainly what we are going to do is to move the sequence control out of the Handlers.
Therefore, we are going to create a new Console Application Project called “BetterChainOfResponsibilityImplementation”.
If you follow the steps as in the next sections in this article, you should end up with this:
IRequest and IResponse
Same as before.
IHandler
What we can notice here:
Removed the void SetNextHandler(IHandler next).
Changed the Handle method signature by adding the IResponse accumulatedResponse = null parameter and renaming the request parameter to be originalRequest.
The newly added accumulatedResponse parameter could be useful if a handler needs to check the previous accumulated changes applied on the response.
Request and Response
Same as before.
HandlerBase
What we can notice here:
Removed the protected IHandler NextHandler.
Actually the whole class can be removed now unless you have some common implementation between all handlers.
We kept it here for demonstration purposes.
Handler1
What we can notice here:
We changed the way the handler processes a request.
Now it just creates a new messages list and returns a new response.
It doesn’t know anything about the previous or next handlers.
Note that we didn’t add here any validations or handle failing cases for simplicity. However, in the real world, this should have been taken care of.
Handler2
Same as Handler1 with replacing the message “Handled by Handler 1” with “Handled by Handler 2”.
AggregateHandler
What we can notice here:
Still the handler here acts as a wrapper handler which would be composed of all the handlers and then be responsible for properly setting the chain.
What is changed here is the way it calls and uses the handlers.
Note that we didn’t add here any validations or handle failing cases for simplicity. However, in the real world, this should have been taken care of.
Program
Same as before.
Running this, you would end up with this result:
Great, it is working as it should.
Final Words
Now you understand the Chain of Responsibility Design Pattern and you know how to enhance the design. It is now up to you to decide which way to go.
My advice to you is to still search for other resources about the Chain of Responsibility Design Pattern. This would help you understand it more and may be you come across some useful tip or trick mentioned by someone else.
Commenti