Click here to Skip to main content
15,860,844 members
Articles / Programming Languages / C#
Article

Refactoring to Patterns: Chain Of Responsibility Pattern

Rate me:
Please Sign up or sign in to vote.
3.43/5 (14 votes)
28 Mar 20055 min read 55.4K   46   7
This article details a real world example of how to use a <i>Chain of Responsibility</i> pattern.

Image 1

Introduction

Developers in .NET sometimes come from scripting language environments that are not strong on Object Oriented methodologies. OO methodologies like refactoring and using design patterns can be intimidating and the value for the developer is hard to see. What developers new to the OO world, and even more seasoned developers need to understand is that good design is not beyond scope in any project. Good habits simply must be learned that will make the developer a better designer.

In that mind set, I am submitting the fourth of several "how to" real world examples, used in my professional life, on how to use patterns in a simple, easy to follow manner. Many of us have ended up either writing or working on in-line, scripting based code. Taking some ideologies from refactoring methodology, is not necessarily a bad thing. Of course, when you are first writing an algorithm or a series of logic statements, it seems easier to start with if....then....else. But as you continue to expand this code to more complex forms, this model can quickly become unmanageable. This is where refactoring and design pattern methodology can play an important and useful part in simplifying, enhancing and making the code more useable (and understandable) to those who practice good object oriented design.

This article details a real world example of how to use a Chain of Responsibility pattern to help you deal with creating an object oriented chain of events from inline code.

Background

I have used the Chain of Responsibility patterns to deal with long if....then....else statements that have successive code written for each path, and each path needs to form a chain. We can make a much more descriptive path by coding it into a framework of successive objects that keep a reference to one another in the path, so that a logical path is built by the objects themselves.

How to use the code

We start off with looking at the classes before the refactoring effort. The first class we will look at is the Approver class, and its three inherited classes, Manager, Director and VicePresident. Notice that each is a simple data object and has neither any idea nor reference to the existence of the other classes.

C#
public abstract class Approver
{
    private string _name;            
    public Approver(string name)
    {
        _name = name;                
    }
    public string Name
    {
        get{return _name;}
        set{_name = value;}
    }        
}
public class Manager : Approver
{
    public Manager(string name) : base(name)
}
public class Director : Approver
{
    public Director(string name) : base(name)
}
public class VicePresident : Approver
{
    public VicePresident(string name) : base(name)
}

Next we see the ChangeType enum, and the ChangeRequest class. The ChangeType enum is a simple way to indicate what type of change the request is, the ChangeRequest class is a data object containing the basic request data, including ChangeType. These two pieces of code will not change for this example, and exist solely for aid in describing the pattern:

C#
public enum ChangeType
{
    Add = 1,
    Modify = 2,
    Remove = 3
}
public class ChangeRequest
{
    private int _requestId;
    private ChangeType _changeType;
    private string _changeMessage;
    private bool _isApproved;
    
    public ChangeRequest(int requestId, ChangeType changeType, 
                                        string changeMessage)
    {
        _requestId = requestId;
        _changeType = changeType;
        _changeMessage = changeMessage;
    }

    public int RequestId
    {
        get{return _requestId;}
        set{_requestId = value;}
    }
                
    public ChangeType TypeOfChange
    {
        get{return _changeType;}
        set{_changeType = value;}
    }
        
    public string ChangeMessage
    {
        get{return _changeMessage;}
        set{_changeMessage = value;}
    }
        
    public bool IsApproved
    {
        get{return _isApproved;}
        set{_isApproved = value;}
    }
}

Next we will see some execution code, in the state it exists in before the refactoring effort. We are creating a ChangeRequest and based on an Approver object (created outside this code block), we allow some measure of processing. This code assumes that several passes may be made into the code block to get different types of approvals. It also does not have the needed chain leading to the next approver. We have no encapsulation of duties, and no way to chain the responsibility between the consecutive approvers.

C#
//approver object is set outside this code example
ChangeRequest changeRequest = 
  new ChangeRequest(1,ChangeType.Remove,"This is a change.");
if(approver is Manager)
{
    if(changeRequest.TypeOfChange.Equals(ChangeType.Add))
        changeRequest.IsApproved = true;
} 
else if(approver is Director)
{
    if(changeRequest.TypeOfChange.Equals(ChangeType.Add) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Modify))            
        changeRequest.IsApproved = true;
} 
else if(approver is VicePresident)
{
    if(changeRequest.TypeOfChange.Equals(ChangeType.Add) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Modify) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Remove))
        changeRequest.IsApproved = true;
                
}

So the first thing we must do to implement the Chain of Responsibility pattern, is to modify the Approver class to have an exact idea of the next Approver, and allow the Approver object type to indicate what actions are to be taken. Notice we change the base Approver class and add SetNextApprover and Approve methods. SetNextApprover is the programmatic way we build the chain, and Approve is the functional method, containing the functional code for the class.

Note: Of course how and where you allow the chained action to be accomplished is more flexible than this example implies. For example, the Approve method could call some function on request or simply be a delegate to another object:

C#
public abstract class Approver
{
    private string _name;
    private Approver _nextApprover;
                
    public Approver(string name)
    {
        _name = name;                
    }
                    
    public string Name
    {
        get{return _name;}
        set{_name = value;}
    }
        
    public Approver NextApprover
    {
        get{return _nextApprover;}
    }
    
    public void SetNextApprover(Approver nextApprover)
    {
        _nextApprover = nextApprover;
    }
        
    public abstract void Approve(ref ChangeRequest changeRequest);
}
public class Manager : Approver
{
    public Manager(string name) : base(name) {}

    public override void Approve(ref ChangeRequest changeRequest)
    {
        //Manager can approve only addition ChangeRequests
        if(changeRequest.TypeOfChange.Equals(ChangeType.Add))
        {
            changeRequest.IsApproved = true;
        } 
        else
        {
            if(NextApprover != null)
                NextApprover.Approve(changeRequest);
        }
        
    }
}
public class Director : Approver
{
    public Director(string name) : base(name) {}

    public override void Approve(ref ChangeRequest changeRequest)
    {
        //Director can approve only add and modify ChangeRequests not remove
        if(changeRequest.TypeOfChange.Equals(ChangeType.Add) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Modify))
        {
            changeRequest.IsApproved = true;
        } 
        else
        {
            if(NextApprover != null)
                NextApprover.Approve(changeRequest);
        }
    }
}

public class VicePresident : Approver
{
    public VicePresident(string name) : base(name) {}
    public override void Approve(ref ChangeRequest changeRequest)
    {
        //VicePresident can approve only add modify and remove
        if(changeRequest.TypeOfChange.Equals(ChangeType.Add) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Modify) ||
            changeRequest.TypeOfChange.Equals(ChangeType.Remove))
        {
            changeRequest.IsApproved = true;
        }
    }
}

Now lets look at the execution code. We see the constructors first (which we could further refactor to a factory that returned the chained objects from a organizational table in a database), and next we see how we set up the actual chain of approval events, passing in the consequent approvers to each class. Now when we create and pass the ChangeRequest object into the Manager object we get a chained approval process that is set up in the code. For the below example, all the three approval chain depths are seen:

C#
Manager manager = new Manager("Joseph");
Director director = new Director("Thomas");        
VicePresident vicePresident = new VicePresident("Jason");
manager.SetNextApprover(director);
director.SetNextApprover(vicePresident);
ChangeRequest changeRequest = 
      new ChangeRequest(1,ChangeType.Add,"This is a change.");
manager.Approve(ref changeRequest); //manager approves
changeRequest = new ChangeRequest(1,ChangeType.Modify,"This is a change.");
manager.Approve(ref changeRequest); //director approvers
changeRequest = new ChangeRequest(1,ChangeType.Remove,"This is a change.");
manager.Approve(ref changeRequest);//VP approves

Points of interest

This is the fourth installment in the series I am writing on real world design patterns. All examples and the bulk of this article are taken from my professional experience as an architect. The examples given are templates only, and the designer must keep in mind that they are the ones who must decide where different patterns, if any, may be best used in their code.

Deciding to perform a refactoring effort from the existing code to a pattern must be weighed on the necessity and need of the code itself. Patterns are only design templates, helpers to accommodate better overall design. I must stress that making an effort to use patterns will strengthen your overall design ability, but like your basic coding skills, it is something that is to be learnt and cultivated.

If this or any other in this series on design patterns is helpful or you have questions or comments please e-mail me at chris.lasater@gmail.com.

History

This is the second revision and is the fourth installment in a series.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United States United States
Christopher G. Lasater

I am also a published author, please check out my book:
ISBN: 1-59822-031-4
Title: Design Patterns
Author:Christopher G. Lasater
More from my web site
Amazon.com


Comments and Discussions

 
GeneralGive credit where credit is due! Pin
Sicle11-Apr-05 13:07
Sicle11-Apr-05 13:07 
GeneralAnother small suggestion Pin
Todd_s0229-Mar-05 11:35
Todd_s0229-Mar-05 11:35 
GeneralRe: Another small suggestion Pin
Christopher G. Lasater1-Apr-05 11:08
Christopher G. Lasater1-Apr-05 11:08 
Generalsmall suggestion Pin
Tom Janssens28-Mar-05 19:47
Tom Janssens28-Mar-05 19:47 
Hi,

First of all, I think your articles are really interesting.
I Have a small suggestion :
In your approver, you call
"if(NextApprover != null) NextApprover.Approve(changeRequest); "
every time, except with the Vice President.
So if a programmer calls "VicePresident.NextApprover=new CEO()" you will have a problem. What i would suggest is creating a wrapping method around the calling of your function (DoApprove), which automaticly calls "if(!changerequest.Approved && NextApprover != null) NextApprover.Approve(changeRequest); " after executing the function.


Tom

Core
GeneralRe: small suggestion Pin
gorsha7428-Mar-05 21:26
gorsha7428-Mar-05 21:26 
GeneralRe: small suggestion Pin
Zhi Chen13-Jul-07 5:56
Zhi Chen13-Jul-07 5:56 
GeneralRe: small suggestion Pin
Christopher G. Lasater29-Mar-05 4:13
Christopher G. Lasater29-Mar-05 4:13 

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.