Click here to Skip to main content
15,879,490 members
Articles / General Programming

Reusable Chain of responsibility in C#

Rate me:
Please Sign up or sign in to vote.
4.76/5 (44 votes)
3 Apr 2014CPOL13 min read 109.2K   890   69   46
This article explains how to make chain of responsiblity pattern reusable with the help of specification pattern.

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:

Image 1

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

C#
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:

C#
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:

C++
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:

C#
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

C#
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.

C#
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:

C#
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.

C#
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).

C#
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:

  1. Employee specification: All basic mobile phones
  2. Supervisor specification: Budget mobiles phone that are less than 200 dollars,
  3. Manager specification: Budget mobile phones that costs more than 200 dollars and premium that costs less than 500 dollars.
  4. 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.

C#
//To extract all mobile phones that costs less than 200
ISpecification<Mobile> budgetLowCostSpec = new Specification<Mobile>(o => (o.Cost < 200));
//To extract all mobile phones that costs greater than or equal to 200
ISpecification<Mobile> budgetHighCostSpec = new Specification<Mobile>(o => (o.Cost >= 200));
 
//To extract all mobile phones that costs less than 500
ISpecification<Mobile> premiumLowCostSpec = new Specification<Mobile>(o => (o.Cost < 500));
//To extract all mobile phones that costs greater than or equal to 500    
ISpecification<Mobile> premiumHighCostSpec = new Specification<Mobile>(o => (o.Cost >= 500));

Now let us re-define the specifications for our new policy:

C#
employee.SetSpecification(basicSpec);
            
// For supervisor spec we combine the budget spec with budget low cost spec to achieve the 
// constraint all budget mobiles that costs less than 200
supervisor.SetSpecification(budgetSpec.And<Mobile>(budgetLowCostSpec));

// For manager spec we combine the budget spec with budget high cost spec to achieve the 
// constraint all budget mobiles that costs more than or equal to 200.
// For manager spec we combine the premium spec with premium low cost spec to achieve the 
// constraint all premium mobiles that costs less than 500. 
manager.SetSpecification(budgetSpec.And<Mobile>(budgetHighCostSpec).Or<Mobile>(premiumSpec.And<Mobile>(premiumLowCostSpec)));
            
// For senior manager spec we combine the premium spec with premium high cost spec to 
// achieve the constraint all premium mobiles that costs more than or equal to 500.   
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;

C#
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.

C#
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:

C#
//Create the different handlers
IHandler<Mobile> seniorManager = new Approver<Mobile>("SeniorManager");
IHandler<Mobile> supervisor = new Approver<Mobile>("Supervisor");
IHandler<Mobile> employee = new Approver<Mobile>("Employee");
 
//Set the specifications for the handlers
employee.SetSpecification(basicSpec);
supervisor.SetSpecification(budgetSpec);
seniorManager.SetSpecification(premiumSpec);
 
//Set the successors
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(seniorManager);
 
//Execute
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.

C#
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:

C#
//The sepcification and successors are assigned through constructor for simplicity
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:

C#
public void HandleRequest(T o)    
{
    if (CanHandle(o))  
    {
         o.Process(); //Processing will be done inside the process method of Mobile object
         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.

C#
public Approver(string name, Action<T> action)      {
            this.name = name;
            this.action = action;
        } 
public void HandleRequest(T o)    
{
    if (CanHandle(o))  {
       //o.Process();
       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

C#
public class InventoryProcess<T>
    {
        public void placeOrder(T o)
        {
            Console.WriteLine("Action is invoked");
            //Place the order.
        }
 
    }  

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

Image 2

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

C#
employee.SetSuccessor(supervisor);
supervisor.SetSuccessor(manager);
manager.SetSuccessor(seniorManager);
seniorManager.SetSuccessor(employee); //Employee is set as the successor here.     

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.

C#
public void HandleRequest(T o)
        {
            if (CanHandle(o))
            {
                //o.Process();
                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:

C#
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:

Image 3

Reference

History

Updated the source code with exception handling.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Switzerland Switzerland
Software engineer since 2004 with mainstream experience in C#, asp.net, linq, sql server and mobile/responsive web application development.

Comments and Discussions

 
AnswerRe: Great ... Pin
Govindaraj Rangaraj26-Mar-14 22:45
Govindaraj Rangaraj26-Mar-14 22:45 
QuestionVotes 5 Pin
ManishHPatil20-Mar-14 11:12
professionalManishHPatil20-Mar-14 11:12 
AnswerRe: Votes 5 Pin
Govindaraj Rangaraj3-Dec-14 4:33
Govindaraj Rangaraj3-Dec-14 4:33 
PraiseMessage Closed Pin
10-Nov-15 8:50
lilyabuzeid10-Nov-15 8:50 
AnswerMessage Closed Pin
5-Aug-18 6:10
mostafakhames5-Aug-18 6:10 
QuestionSeems that the performance of your specification parttern will not be fast enough Pin
comiscience17-Mar-14 6:59
comiscience17-Mar-14 6:59 
AnswerRe: Seems that the performance of your specification parttern will not be fast enough Pin
Govindaraj Rangaraj18-Mar-14 0:17
Govindaraj Rangaraj18-Mar-14 0:17 
GeneralRe: Seems that the performance of your specification parttern will not be fast enough Pin
comiscience18-Mar-14 1:17
comiscience18-Mar-14 1:17 
Thank you Govin
QuestionArticle Looking good Pin
sudhir77815-Mar-14 3:42
sudhir77815-Mar-14 3:42 
QuestionVery good article Pin
Jasper4C#14-Mar-14 23:57
Jasper4C#14-Mar-14 23:57 
AnswerRe: Very good article Pin
Govindaraj Rangaraj18-Mar-14 0:47
Govindaraj Rangaraj18-Mar-14 0:47 
GeneralMy vote of 5! Pin
Rafael Nicoletti14-Mar-14 16:03
Rafael Nicoletti14-Mar-14 16:03 
GeneralRe: My vote of 5! Pin
Govindaraj Rangaraj26-Mar-14 22:45
Govindaraj Rangaraj26-Mar-14 22:45 
QuestionThis article was well written Pin
Member 1020865914-Mar-14 14:40
Member 1020865914-Mar-14 14:40 
AnswerRe: This article was well written Pin
Govindaraj Rangaraj26-Mar-14 22:57
Govindaraj Rangaraj26-Mar-14 22:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.