Prerequisites
To fully understand this article you must be familiar with the Chain Of Responsibility (CoR) design pattern. On this website there are some good articles that can help you. If you are not familiar with CoR please read some of them and then come back here. If you already know what CoR is - please continue.
Refactoring to Patterns: Chain Of Responsibility Pattern
Chain of Responsibility
Introduction
This article discusses an extension of the Chain of Responsibility (CoR) design pattern. The pattern is used to reduce the complexity of some program logic. With ease the client can change the flow of the logic statically or dynamically. He can also add new actions that must be taken. It introduces a way to share code using aggregation and interfaces. The client can also predefine the behavior of handling the specific request.
First I am going to present you with the library itself - it is small and contains just four objects. The UML diagram describes the relationships between them. Then I am going to describe their responsibilities. After that a description of each kind of object follows. There are some "tips and tricks" using the library depending on the variety of requirements you want to satisfy. After that a real world example using the library is present.
Background
I had to write a component for handling customer requests. There were many if
s and switch
es within the complex logic of handling the request. In some handling cases the same actions had to be executed. The requirements and the structure of the logic were changing often and I had to have flexibility in assigning the required responsibilities and actions.
Using the Code - The Core Library
In the extension a separation of the objects responsibilities has been made. A Handler
object is one that decides if he should handles a request. An Action
is an object that does the real job with the request. Every Handler
has attached Action
s to it. If he has to handle the request - he just passes it to his Actions
. If not - it behaves just like the normal Chain of Responsibility - he just passes it to the next Handler
in the chain. The behaviour of the library can be very easily changed.
The Actions
If you want an action to be taken when a handler must handle a specific request, you should create a XXXAction
class. You should implement the IAction<TContext>
interface by writing an imlpementation of Execute
method. The TContext
in most cases is the request itself. If you want a subscriber to be notified when an action is executed you can inherit from the Action<TContext, TExecutedEventArgs>
abstract class. Normally the event should be fired when the action has executed successfully. You can use the protected RaiseExecutedEvent
method for raising the event.
public class ActionWithEvent : IAction<string, EventArgs>
{
public override void Execute(string context)
{
try
{
RaiseExecutedEvent(this, EventArgs.Empty);
}
catch (SomeSpecificException ex)
{
}
}
}
The first benefit of using these XXXAction
classes is that they can easily be reused in different Handler
s. Through the interface you can also easily create adaptors that call your other, already written logic.
The Handlers - Handle Algorithm
When you want to create a handler you can implement the IHandler<TRequest>
interface. The prefered way to do it is to inherit from the Handler<TRequest>
class. It has written with all common functionality and has provided the client the possibility to change its default behavior. The only necessary method you must implement is MustExecuteHandleActions
which in other words means "should I handle the request." The default behaviour of the Handler
is:
public virtual void Handle(TRequest request)
{
bool mustExecuteHandleActions = MustExecuteHandleActions(request);
if (mustExecuteHandleActions)
ExecuteHandleActions(request);
bool mustPassRequestToNext = !mustExecuteHandleActions
|| mAlwaysPassRequestToNextHandler;
if (mustPassRequestToNext)
PassRequestToNextHandler(request);
}
The algorithm is simple - if the handler must handle the request then all the actions are executed. If not - the request is passed to the next handler. The way you can use the handler is by calling his Handle
method and pass him the request.
SomeHandler handler = new SomeHandler();
SomeRequest request = new SomeRequest();
handler.Handle(request);
The Handlers - Actions
The Handler
object has a collection of IActions
. You add them through the AddHandleAction
method.
SomeHandler handler = new SomeHandler();
handler.AddHandleAction(new ConcreteAction());
They are executed in the way added in the collection. If you want to change the default behaviour of execution of the actions you should override the ExecuteHandleActions
method.
The Handlers - Passing the Request to the Next Handler
The Handler
keeps the next Handler
which he eventually passes the request to. You can set it through the NextHandler
property.
FirstHandler first = new FirstHandler();
SecondHandler second = new SecondHandler();
first.NextHandler = second;
Thus you can construct the chain of Handler
s, which, one by one receive the request until one of them (by the default behaviour) handles it. If you want all the Handler
s in a concrete chain to receive and try to handle the request you should set their AlwaysPassRequestToNextHandler
to true.
FirstHandler first = new FirstHandler();
first.AlwaysPassRequestToNextHandler = true;
SecondHandler second = new SecondHandler();
second.AlwaysPassRequestToNextHandler = true;
ThirdHandler third = new ThirdHandler();
first.NextHandler = second;
second.NextHandler = third;
SomeRequest request = new SomeRequest();
first.Handle(request);
This way all the three Handler
s will receive the request.
The Handlers - The Request As a Way The Handlers Communicate
The request itself can be used as a way for Handler
s to communicate in some way. You can set some properties of the request which the next Handler
can use when it decides if it should handle it. Yes or not, if the AlwaysPassRequestToNextHandler
is set to true - the request is passed to the next Handler
.
The Handlers - Implementing a Tree Instead of a Chain
Creating a simple chain has some disadvantages. For example, it can become too long, which slows the process. In such a case you can create a tree of Handler
s. It is prefered though not to complicate the tree more than just one if-then-else
construction because if you place a complex switch inside it, the whole pattern becomes useless, as this is one of the problems it solves. The easiest way to create such a handler is by doing a few little steps. First: most probably you don't want your simple-if-handler to execute some Action
s so you override the MustExecuteHandleActions
to return false. Second: you can place the logic of passing the request in overriden PassRequestToNextHandlers
method. But you must have instantiated the class with two references of Handler
objects.
public class IfHandler : Handler<string>
{
private Case1Handler case1Handler;
private Case2Handler case2Handler;
public IfHandler(Case1Handler case1, Case2Handler case2)
{
case1Handler = case1;
case2Handler = case2;
}
public override bool MustExecuteHandleActions(string request)
{
return false;
}
public override void PassRequestToNextHandlers(string request)
{
if (request != null)
case1Handler.Handle(request);
else
case2Handler.Handle(request);
}
}
Overriding MustExecuteHandleActions
to return false is not mandatory. If you still want some Action
s to be executed you can implement it. But in order to sucessfully pass (if needed of course) the request to one of the next two handlers - you should set the AlwaysPassRequestToNextHandler
to true.
Using the Code - Real World Example
Imagine you have to write a component that handles customer requests. The request is for a current product the customer wants to buy. It arrives and you have to check for its request number. Thus we create an InitialCustomerRequestHandler
. If the request number is invalid we have to generate it. Thus we create AssignUniqueGuidAction
and assign it to the InitialCustomerRequestHandler
. We set the AlwaysPassRequestToNextHandler
to true so that in both cases the next Handler
receives the request.
Then we have to check for product availability. We will not create a simple handler, but one that doesn't have actions and has an if clause which decides to which of its child handlers to pass the request to. We call it ProductAvailabilityHandler
.
If the product is not available we have to report that to someone reponsible and we have to offer the customer some of our new products. Thus we create a UnavailableProductHandler
and assign to him two new actions - ReportUnavailableProductAction
and OfferCustomerNewProductsAction
.
In the case when the product is available we have to check if the customer is new. We create a NewCustomerHandler
and assign it PriceOfferAction
. We also assign it the already created OfferCustomerNewProductsAction
.
If the customer is not new the request is received by the RegularCustomerHandler
. It has attached to it the already created PriceOfferAction
and OfferCustomerNewProductsAction
. It also uses the new PriceReductionAction
.
Benefits
The extension inherits all benefits from the original CoR - reduced coupling between the sender of the request and the concrete handler of the request.
It has the flexibility of assigning responsibilities — the chain can be built and manipulated dynamically. The extension provides flexibility in assigning the concrete actions.
The main plus is that we separate the logic of the objects between different types of classes - handlers and actions. The actions can be easily reused.
The concrete handlers can be easily unit tested using mock actions. If the logic was inside the request handler it would be impossible to write the tests.
One benefit of the concrete implementation is that it is small, readable and easily used and configurable.
Drawbacks
Besides the good sides of the extension it still suffers the same problems as original CoR. That is - a request is not guaranteed to be handled.
There are also problems such as hardened debugging of the chain of handlers and the execution of the concrete action.
Another drawback is the complicated creation and configuration of the chain.
History
Version 1.0 sent on 16.08.2007.