-->

09/12/2011

Behavioral Pattern - Chain of Responsibilities Using Extensibility Framework


"Change is good and Inevitable", this is what i said in my earlier post.
But not all changes are good. That is why we have Change management system in place in IT.
Because some changes will be like this :

For instance think about http handlers in .Net framework. Each handler will try to validate the incoming request. If a request is validated by a handler, it will serve the request, else it will pass on to next handler.
once it is not approved by any handlers, it will be given to Forbidden handler (403).

If you observe properly, there are two kinds of behaviors here.
1. Transmitting the request to next level if not qualified.
2. Process the request if Qualified.
Both objects can process the request but behavior will be decided based up on the request content.

This is the scenario, where Chain of responsibility pattern will come into picture.
Lets evaluate this more in most generic scenario we all know.
"CHANGE REQUEST MANAGEMENT".
Lets assume there are 4 characters:
1. Developer (who can approve only code changes) - Pratap
2. Team Lead (Who can approve module changes) - Sridhar
3. Architect (Who can approve design changes) - Mohan
4. program Manager (Who can approve budget changes) - Subhankar

Don't forget you will have some weird changes like one shown in above image. :)
Lets design a system step by step to see how "Chain of responsibilities" pattern can achieved above requirement.

Step 1: Create a class for roles.
namespace DP_ChainOfResponsibility
{
    public class Roles
    {
        public const string PO = "ProductOwner";
        public const string BA = "Business Analyst";
        public const string DEV = "Developer";
        public const string TL = "Team Lead";
        public const string ARC = "Architect";
        public const string MGR = "Project Manager";
    }
}
Step 2: Create class for ChangeRequest.
namespace DP_ChainOfResponsibility
{
    public class ChangeRequest
    {
        public int ChangeRequestType { get; set; }
        public string Requestor { get; set; }
    }
    
    public enum ChangeType
    {
        CodeChange = 1,
        ModuleChange = 2,
        DesignChange = 3,
        BudgetChange = 4,
        weirdChange=5
    }
}
Step 3: Create an interface for request handling.
namespace DP_ChainOfResponsibility
{
    public interface IRequestHandler
    {
         string Name { get; set; }
         void HandleChangeRequest(ChangeRequest CR);
         IRequestHandler NextLevel { get; set; }
    }
    
}
Step 4: We gonna implement this action of handling the request to next level via a extension method which can be applicable to all the classes. This is typically called "Managed Extensibility Framework (MEF)".
namespace DP_ChainOfResponsibility
{
    static class RequestHandlerExtension
    {
        public static void TryNextLevel(this IRequestHandler current, ChangeRequest req)
        {

            if (current.NextLevel != null)
            {
                Console.WriteLine("CR Type : {2}, {0} Can't approve - Passing CR to {1}", current.Name, current.NextLevel.Name,
                    req.ChangeRequestType==2?"Module Change":req.ChangeRequestType==3?"Design Change":req.ChangeRequestType==4?"Budget Change":"weird Change");
                current.NextLevel.HandleChangeRequest(req);
            }
            else
            {
                Console.WriteLine("Weird request cannot be served. :)");
            }
        }
    }  
}
Step 5: Now create different classes for each role and handle the request if qualified, else pass on to next level by using the common extension method defined in above step.
//Developer
namespace DP_ChainOfResponsibility
{
    public class Developer:IRequestHandler
    {
        public string Name { get; set; }

        public void HandleChangeRequest(ChangeRequest CR)
        {
            if (CR.ChangeRequestType == 1)
            {
                Console.WriteLine("CR Type: {0}, Requestor: {1}, Approved By: {2}", "Code Change", 
                    CR.Requestor, Name + "(" + this.GetType().ToString().Replace("DP_ChainOfResponsibility.","") + ")");
            }
            else
            {
                this.TryNextLevel(CR);
            }            
        }
        public IRequestHandler NextLevel { get; set; }
    }
}
//Team Lead
namespace DP_ChainOfResponsibility
{
    public class TeamLead : IRequestHandler
    {
        public string Name { get; set; }

        public void HandleChangeRequest(ChangeRequest CR)
        {
            if (CR.ChangeRequestType == 2)
            {
                Console.WriteLine("Request Type: {0}, Requestor: {1}, Approved By: {2}", "Module Change", 
                    CR.Requestor, Name + "(" + this.GetType().ToString().Replace("DP_ChainOfResponsibility.", "") + ")");
            }
            else
            {
                this.TryNextLevel(CR);
            }
        }
        public IRequestHandler NextLevel { get; set; }
    }
}
//Architect
namespace DP_ChainOfResponsibility
{
    public class Architect : IRequestHandler
    {
        public string Name { get; set; }

        public void HandleChangeRequest(ChangeRequest CR)
        {
            if (CR.ChangeRequestType == 3)
            {
                Console.WriteLine("CR Type: {0}, Requestor: {1}, Approved By: {2}", "Design Change",
                    CR.Requestor, Name + "(" + this.GetType().ToString().Replace("DP_ChainOfResponsibility.", "") + ")");
            }
            else
            {
                this.TryNextLevel(CR);
            }
        }
        public IRequestHandler NextLevel { get; set; }
    }
}
//Program Manager
namespace DP_ChainOfResponsibility
{
    public class ProgramManager : IRequestHandler
    {
        public string Name { get; set; }

        public void HandleChangeRequest(ChangeRequest CR)
        {
            if (CR.ChangeRequestType == 4)
            {
                Console.WriteLine("CR Type: {0}, Requestor: {1}, Approved By: {2}", "Budget Change",
                    CR.Requestor, Name + "(" + this.GetType().ToString().Replace("DP_ChainOfResponsibility.", "") + ")");
            }
            else
            {
                this.TryNextLevel(CR);
            }
        }
        public IRequestHandler NextLevel { get; set; }
    }
}
Step 6: All the infra structure is ready. Now go to program.cs and create different kinds of Chage requests, and assign them to developer.
See what will happen.
namespace DP_ChainOfResponsibility
{
    class Program
    {
        static void Main(string[] args)
        {
            //Change Request Details
            var codeChange = new ChangeRequest() { ChangeRequestType=(int)ChangeType.CodeChange,Requestor=Roles.PO};
            var moduleChange = new ChangeRequest() { ChangeRequestType = (int)ChangeType.ModuleChange, Requestor = Roles.BA };
            var designChange = new ChangeRequest() { ChangeRequestType = (int)ChangeType.DesignChange, Requestor = Roles.PO };
            var budgetChange = new ChangeRequest() { ChangeRequestType = (int)ChangeType.BudgetChange, Requestor = Roles.BA };
            var weirdChange = new ChangeRequest() { ChangeRequestType = (int)ChangeType.weirdChange, Requestor = Roles.PO };

            //Approvers, chained together  
            var manager = new ProgramManager() { Name = "Subhankar" };
            var architect = new Architect() { Name = "Mohan", NextLevel = manager };
            var teamlead = new TeamLead() { Name = "Sridhar", NextLevel = architect };
            var developer = new Developer() { Name = "Pratap", NextLevel = teamlead };
            

            //All CR Developer is first to approve  
            developer.HandleChangeRequest(codeChange);
            Console.WriteLine("---------------------------------");
            developer.HandleChangeRequest(moduleChange);
            Console.WriteLine("---------------------------------");
            developer.HandleChangeRequest(designChange);
            Console.WriteLine("---------------------------------");
            developer.HandleChangeRequest(budgetChange);
            Console.WriteLine("---------------------------------");
            developer.HandleChangeRequest(weirdChange);
            Console.WriteLine("---------------------------------");
            Console.ReadLine();  
        }
    }
}
Output:
Observe the out put, and you can see that even though all the requests were assigned to developer, they were carried out through change request management process and right requests were approved by right people.

This is a simple way of implementing "Chain of responsibilities pattern" using Extensibility Framework.
Code:
Click Here
Is it helpful for you? Kindly let me know your comments / Questions

No comments:

Post a Comment