Introduction
Chain of responsibility (COR) is a pattern which helps us to avoid coupling between sender and receiver. Mediator/Observer patterns do the same job but the link between the receivers (chain) in COR makes it stand out from the others. In other words, the request will be passed between different receivers in sequential chain until the right receiver is met.
Below is the class diagram for the classic chain of responsibility pattern:
COR is a very useful pattern in scenarios such as workflow. It is always handy when we have a reusable workflow design to be used across different domain with in the same organization or between different organizations. However the classic COR is not flexible enough to be reused as it is bound to the domain models in the implementation. Also changes to the chain cannot be done without modifying the code in all scenarios. In this article I explain how we can use specification pattern to close COR code for future modification and make it flexible and reusable.
Chain of Responsibility
Let us implement the COR using an example. Consider a mobile phone store where we have mobile phones of different types (basic, budget and premium). Let us define the mobile phone class
public class Mobile : IProcessable
{
public Type Type { get; set; }
public double Cost;
public string GetDescription()
{
return "The mobile is of type : " + this.Type;
}
public Mobile(Type type, int cost = 0)
{
this.Type = type;
this.Cost = cost;
}
public void Process()
{
this.Cost = (0.9) * this.Cost;
Console.Write("The new cost is: {0} and the type is {1}. ",
this.Cost, this.Type);
}
}
public enum Type
{
Basic,
Budget,
Premium
}
The mobile class consists of two main properties: Type and cost. The Type is of type enum
and cost is of type double.
Let us define the following chain of responsibility scenario. The store has a policy of inventory purchase as follows: Employees can only place order for basic mobiles phones. Supervisors can make orders for budget types only. Whereas the senior manager is the one who can place orders for premium type mobile phones.
A classic implementation of the chain of responsibility for this scenario is given below:
abstract class Handler
{
protected Handler successor;
public void SetSuccessor(Handler successor)
{
this.successor = successor;
}
public abstract void HandleRequest(Mobile mobile);
}
class Employee : Handler
{
public override void HandleRequest(Mobile mobile)
{
if (CanHandle(mobile))
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, mobile);
}
else if (successor != null)
{
successor.HandleRequest(mobile);
}
}
public bool CanHandle(Mobile mobile)
{
return (mobile.Type == Type.Basic);
}
}
The interface handler defines two methods SetSuccessor
and HandleRequest
. SetSuccessor
method is where we form the chain between different handlers (in our case the store hierarchy of employee, supervisor and senior managers). The handle request method will check if the object in question (mobile) can be handled by the current handler. If not, it will be passed to the next handler in chain by calling successor. HandleRequest
method.
The employee class accepts a mobile object and check if it can be handled by itself. The business rule for the employee is that the mobile should be of Type basic, which is implemented in the CanHandle
method. If it is true the request will be processed else it is passed to the successor of employee. Example implementation for supervisor and senior manager classes that are successors of employee and supervisor respectively can be seen below:
class Supervisor : Handler
{
public override void HandleRequest(Mobile mobile)
{
if (CanHandle(mobile))
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, mobile);
}
else if (successor != null)
{
successor.HandleRequest(mobile);
}
}
public bool CanHandle(Mobile mobile)
{
return (mobile.Type == Type.Budget);
}
}
class SeniorManager : Handler
{
public override void HandleRequest(Mobile mobile)
{
if (CanHandle(mobile))
{
Console.WriteLine("{0} handled request {1}",
this.GetType().Name, mobile);
}
else if (successor != null)
{
successor.HandleRequest(mobile);
}
}
public bool CanHandle(Mobile mobile)
{
return (mobile.Type == Type.Premium);
}
}
The code above is mere repetition of the employee class with the business rule inside CanHandle
method being the only exception. Let us see how these are executed:
Handler employee = new Employee();
Handler supervisor = new Supervisor();
Handler seniorManager = new SeniorManager();
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(seniorManager);
mobiles.ForEach(o => employee.HandleRequest(o));
After creating objects for each handler we chain them using the SetSuccessor
method and start the processing by calling the <span style="font-size: 10.5pt; font-family: 'Segoe UI',sans-serif; color: rgb(17, 17, 17);"><code>HandleRequest
method of the employee handler (first object in the chain).
Limitations
This code works without any flaw. But there are some limitations
Open/Closed principle
This implementation supports the open/closed principle but only to some extent. In classic COR there goes the saying, the behavior of the system can be altered by adding new handlers or by changing the order of the chain without modifying the code. But this is not true for all scenarios. Let us see this by example. Imagine the store wants to offload the work of supervisor and senior managers by introducing another level called managers. Managers should approve all the budget type mobile phones that costs more than 200 dollars. They should also approve all the premium types that costs less than 500. This condition implies we should accommodate the manager in between supervisor and senior manager in the chain. Here is how we can achieve this:
Create a new class called manager that implements IHandler
and copy the same code from, say, employee and change the <span style="font-size: 10.5pt; font-family: 'Segoe UI',sans-serif; color: rgb(17, 17, 17);"><code>CanHandle
method as follows
public bool CanHandle(Mobile mobile)
{
return ((mobile.Type == Type.Budget && mobile.Cost >= 200)
|| (mobile.Type == Type.Premium && mobile.Cost < 500));
}
Are we done yet? the answer is a NO. We need to change the business rule with in canhandle method of supervisor and SeniorManager to restrict them from handling all budget and premium type phones with cost constraints. Hence the code is not closed for modification in this type of scenarios.
Re-usability
Can we re-use this implementation for another business domain or project? Imagine a new domain where the handlers are author, editor and community moderators of codeproject and the requests are codeproject articles.
Conversation between COR and specification pattern
- COR: Awww, I didn't know I have these problems? How can I save myself?
- Specification pattern: Oh…hello! Don’t worry I am here to the rescue!
- COR: Really??? How?
- Specification pattern: read further
Specification pattern
In specification pattern, business rules are segregated based on the Single responsibility principle (SRP) and can be chained or composed using boolean operands (AND, OR or NOT) to create complex business rules. Each segregated business rule is called Specification. I have already written a detailed article on this topic. Please click here to find the article on CodeProject. In this article I have demonstrated how to implement the specification pattern in classical way using C#. Let us briefly see this works. The father of the specification pattern is the interface ISpecification
.
public interface ISpecification<T>
{
bool IsSatisfiedBy(T o);
}
Every specification must implements this interface. The actual business rule goes in the IsSatisfiedBy
method. Let us see how a specification can be implemented:
public class Specification<T> : ISpecification<T>
{
private Func<T, bool> expression;
public Specification(Func<T, bool> expression)
{
if (expression == null)
throw new ArgumentNullException();
else
this.expression = expression;
}
public bool IsSatisfiedBy(T o)
{
return this.expression(o);
}
}
Each business rule can be defined as an expression that is evaluated to a Boolean result. For example, checking the type of mobile phone for premium or not is an expression. We can define this type of expression in C# as Func<T, bool>
where T is the object (Mobile) on which the rule (check for premium type) is evaluated and bool is the result. The IsSatisfiedBy
method above calls this expression and returns the Boolean result of the expression.
As I mentioned earlier, the specifications can be chained/composed using the boolean operands. In my previous article I have achieved this by defining dedicated specifications for each operand. The same functionality can also be achieved using the extension methods in C# as demonstrated by Yury Goltsman in the comments section. The code below shows how to chain the specifications using Boolean AND, OR and NOT operands.
public static class SpecificationExtensions
{
public static Specification<T> And<T>(this ISpecification<T> left, ISpecification<T> right)
{
return new Specification<T>(o => left.IsSatisfiedBy(o) && right.IsSatisfiedBy(o));
}
public static Specification<T> Or<T>(this ISpecification<T> left, ISpecification<T> right)
{
return new Specification<T>(o => left.IsSatisfiedBy(o) || right.IsSatisfiedBy(o));
}
public static Specification<T> Not<T>(this ISpecification<T> left)
{
return new Specification<T>(o => !left.IsSatisfiedBy(o));
}
}
As you can see, each method (AND, OR and NOT) takes one or more specification and returns a specification. As mentioned earlier, anything that accepts an expression and returns Boolean result can be defined as specification. Same concept is used here. Let us see the example of AND method. It accepts two different specifications and builds a new specification by using the expression " left.IsSatisfiedBy(t) && right.IsSatisfiedBy(t)
". As we already knew that the return type of IsSatisfiedBy
method is Boolean, hence we can easily deduce that this expression is nothing but && operation applied on two Boolean values. Similar is the case for OR and NOT methods.
Let us implement the store policy discussed above using this specification pattern (filter out the mobile phones that are of types basic, budget and premium).
ISpecification<Mobile> basicSpec = new Specification<Mobile>(o => o.Type == Type.Basic);
ISpecification<Mobile> budgetSpec = new Specification<Mobile>(o => o.Type == Type.Budget);
ISpecification<Mobile> premiumSpec = new Specification<Mobile>(o => (o.Type == Type.Premium));
var mobilesHandledByEmployee = mobiles.FindAll(o => basicSpec.IsSatisfiedBy(o));
mobilesHandledByEmployee.ForEach(o => Console.WriteLine(o.GetDescription()));
Now let us see how we can implement the change of policy as discussed earlier. The brief summary of the new policy is as follows:
- Employee specification: All basic mobile phones
- Supervisor specification: Budget mobiles phone that are less than 200 dollars,
- Manager specification: Budget mobile phones that costs more than 200 dollars and premium that costs less than 500 dollars.
- Senior manager specification: Premium mobile phones that costs more than 500 dollars.
In order to achieve this we need to create four new specs. BudgetLowCostSpec
filters all mobile phones (note: all mobile phones not just budget) that costs less than 200 and BudgetHighCostSpec
defines all the mobile phones that costs more than or equal to 200. Similarly premiumLowcostSpec
and premiumHighCostSpec
defines all mobile phones that costs less than 500 and more than or equal to 500 respectively.
ISpecification<Mobile> budgetLowCostSpec = new Specification<Mobile>(o => (o.Cost < 200));
ISpecification<Mobile> budgetHighCostSpec = new Specification<Mobile>(o => (o.Cost >= 200));
ISpecification<Mobile> premiumLowCostSpec = new Specification<Mobile>(o => (o.Cost < 500));
ISpecification<Mobile> premiumHighCostSpec = new Specification<Mobile>(o => (o.Cost >= 500));
Now let us re-define the specifications for our new policy:
employee.SetSpecification(basicSpec);
supervisor.SetSpecification(budgetSpec.And<Mobile>(budgetLowCostSpec));
manager.SetSpecification(budgetSpec.And<Mobile>(budgetHighCostSpec).Or<Mobile>(premiumSpec.And<Mobile>(premiumLowCostSpec)));
seniorManager.SetSpecification(premiumSpec.And<Mobile>(premiumHighCostSpec));
Employee spec is unchanged however the other specs are changed by composing the existing spec with new specs using Boolean AND and OR to arrive to our solution. This is the power of the specification pattern, The existing specifications are not necessarily altered. We can create new specs and append to the existing ones as necessary. Isn't it cool?
Rescue mission:
open/closed principle
As we have seen in COR that the only difference between the different handlers is the business rule (CanHandle
method implementation). If we could generalize this, we would be able to close the code for future modifications. So let us see how we can make the business rules generic by using specification pattern. Let us first add a new method definition to the interface IHandler
called SetSpecification
;
public interface IHandler<T>
{
void SetSuccessor(IHandler<T> handler);
void HandleRequest(T o);
void SetSpecification(ISpecification<T> specification);
}
The SetSpecification method accepts a specification object specific to the handler. The handler class is now restricted to handle only objects that implements IProcessable
. We will come to it later.
Let us have a look at the implementation of the handler class. Remember we are going to make this implementation generic which means, we will implement it only once for all the handlers.
public class Approver<T> : IHandler<T> where T: IProcessable
{
private IHandler<T> successor;
private string name;
private ISpecification<T> specification;
public Approver(string name) {
this.name = name;
}
public bool CanHandle(T o) {
return this.specification.IsSatisfiedBy(o);
}
public void SetSuccessor(IHandler<T> handler) {
this.successor = handler;
}
public void HandleRequest(T o) {
if (CanHandle(o)) {
o.Process();
Console.WriteLine("{0}: Request handled by {1}. ", o, this.name);
}
else {
this.successor.HandleRequest(o);
}
}
public void SetSpecification(ISpecification<T> specification) {
this.specification = specification;
}
}
As you see the SetSpecification
method is used to assign a specification pertaining to the handler. The rest of the methods are same except the CanHandle
. In CanHandle
, instead of specifying the concrete business rule we call the IsSatisfiedBy
method of the specification object. Now our code is reusable across any handler and across any domain.
Re-Usability:
We have not specified our domain object (Mobile) on which the COR is acting upon which clearly states it's re-usability. Also note that our specification pattern itself is generic which can easily go along with COR for different projects.
Final solution
Let us implement our older policy first using the COR and specification mix. Our three business specifications are mobile phones of type basic, budget and premium respectively. We use specification pattern to define these rules as follows:
IHandler<Mobile> seniorManager = new Approver<Mobile>("SeniorManager");
IHandler<Mobile> supervisor = new Approver<Mobile>("Supervisor");
IHandler<Mobile> employee = new Approver<Mobile>("Employee");
employee.SetSpecification(basicSpec);
supervisor.SetSpecification(budgetSpec);
seniorManager.SetSpecification(premiumSpec);
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(seniorManager);
mobiles.ForEach(o => employee.HandleRequest(o));
Isn't it tidier than the classic way? First we define the approvers then assign the specifications to each approver accordingly. Define the successors. What else do you need? Just call the HandleRequest
method.
Requirement Change
Now let us see how we implement the change of policy in our code. We need to introduce a new approver called manager who can approve all the budget types that costs more than 200 dollars and all premium types that costs less than 500 Dollars. With simple recreation of the objects we can achieve this as follows.
IHandler<Mobile> seniorManager = new Approver<Mobile>("SeniorManager");
IHandler<Mobile> manager = new Approver<Mobile>("Manager");
IHandler<Mobile> supervisor = new Approver<Mobile>("Supervisor");
IHandler<Mobile> employee = new Approver<Mobile>("Employee");
employee.SetSpecification(basicSpec);
supervisor.SetSpecification(budgetSpec.And<Mobile>(budgetLowCostSpec));
manager.SetSpecification(budgetSpec.And<Mobile>(budgetHighCostSpec).Or<Mobile>(premiumSpec.And<Mobile>(premiumLowCostSpec)));
seniorManager.SetSpecification(premiumSpec.And<Mobile>(premiumHighCostSpec));
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(manager);
manager.SetSuccessor(seniorManager);
mobiles.ForEach(o => employee.HandleRequest(o));
BINGO! Similarly we can change the handlers with different handling object as follows:
IHandler<Article> commModerator = new Approver<Article>("CommunityModerator", commModeratorSpec, null);
IHandler<Article> editor = new Approver<Article>("Editor", editorSpec, commModerator);
IHandler<Article> author = new Approver<Article>("Author", authorSpec, editor);
author.HandleRequest(o);
Did you notice the same COR/Spec mix is used here but for a CodeProject article submission process. Here the handlers are author, editor and community moderators (well, the process flow is tweaked here). The request is an Article. For simplicity purpose the specification and successor are accepted through constructor which means the interface of the Approver
has to be changes accordingly.
Processing
We have our solution but the processing is still not done on the mobile phone. In our case, making the inventory purchase order. Let us make our mobile phone implement the interface IProcessable
that has a method called process. Let us see how we do the processing in the HandleRequest
method:
public void HandleRequest(T o)
{
if (CanHandle(o))
{
o.Process();
Console.WriteLine("{0}: Request handled by {1}. ", o, this.name);
}
else {
this.successor.HandleRequest(o);
}
}
This part can be handled in different ways for different requirements. For example if the processing has to be done outside the context of mobile object then we can use the <Action>
type and invoke it by passing the mobile object as method parameter.
public Approver(string name, Action<T> action) {
this.name = name;
this.action = action;
}
public void HandleRequest(T o)
{
if (CanHandle(o)) {
this.action.Invoke(o);
Console.WriteLine("{0}: Request handled by {1}. ", o.ToString(), this.name);
}
else {
this.successor.HandleRequest(o);
}
}
The method that places the inventory order can look something like this
public class InventoryProcess<T>
{
public void placeOrder(T o)
{
Console.WriteLine("Action is invoked");
}
}
Source code
The complete source code is attached at the top of this article for you to download. The output of the above solution is
Other factors that affects the chain
Here I have consolidated few factors that affect the chain of responsibility pattern due to different needs.
Circular chain
What if we want a circular reference where the last member (Approver
) of the chain has successor as the first member.
Solution
This is simple with this approach. We just need to add the following line when setting the successor for SeniorManager
as follows
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(manager);
manager.SetSuccessor(seniorManager);
seniorManager.SetSuccessor(employee);
Isn't it intuitive? The main benefit of this approach is we need not change the code to make it either circular or linear, it all depends on your configuration (object creations).
However we should be very careful with the specifications. The specifications that span across all the handlers should cover all the possibilities of the requests. For example we have specification that define the cost as <= 200 which means it covers all the negative values. Similarly we have a specification that is defined as >= 500 which covers all possible values from 500 to infinity. Let us say we have a specification instead of cost <= 200 we have cost >= 2 && cost <= 200 then we have a problem. The system goes into infinite loop and throws System.StackOverflowException
.
Passing a list as request
Passing set of requests and handling them through the chain.
Solution
Simplicity is best! So we handle all the requests in loop (calling chain for every request) as implemented in the demo source code (attached above) than passing the list and handling the complexities of maintaining the list items with in the approver
class. However it is not impossible to do it if required. We need to create new approver class, for instance ListApprover
that accepts list of requests and handle them accordingly.
Dynamically adding approvers
Adding new approvers dynamically on the go.
Solution
I have already demonstrated this in the requirements change section.
Overlapping specifications
Imagine there is a scenario where there are overlapping specifications as follows: Two employees Employee1
and Employee2
have to handle all the requests once by each before passing it to supervisor. Hence both the employees have the same specifications defined. In the current approach only one of them will be able to handle the request.
Solution
In order to achieve this we need to tweak our Approver
class as follows.
public void HandleRequest(T o)
{
if (CanHandle(o))
{
Console.WriteLine("{0}: Request handled by {1}. ", o.ToString(), this.name);
this.action.Invoke(o);
Console.WriteLine("\n****************************************");
}
if (this.successor != null)
{
this.successor.HandleRequest(o);
}
}
Here the else part is removed in order to force the successor to handle the request instead of stopping the chain after a request is handled.
Default specification/handler
Contrary to overlapping specification factor, there is a scenario where we do not have specification for certain condition. Imagine we have defined specifications for mobile phone that costs from $10 to infinity and decided not to handle mobile phones that costs less than $10.
Solution:
We need to define a default approver
what will handle this exception scenario (<$10) as follows:
IHandler<Mobile> defaultApprover = new Approver<Mobile>("Default", invProcess.defaultOrder);
ISpecification<Mobile> defaultSpec = new Specification<Mobile>(o => true);
defaultApprover.SetSpecification(defaultSpec);
seniorManager.SetSuccessor(defaultApprover);
Here we have defined a new approver called defaultApprover
and a new specification called defaultSpec
which will always returns true. After applying the default spec to default approver we link this approver to the last member in the chain which in our case is SeniorManager
. Please note, in the first line there is a method called defaultOrder which I have defined inside the InventoryProcess
class explained in the "processing" section above. This method does the necessary processing for all the defaults conditions.
The output sample would be:
Reference
History
Updated the source code with exception handling.