Chain of Responsibility Pattern
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,
this 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 code
more useable (and understandable) to those who practice good OO 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 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 all 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 it's three
inherited classes, Manager
, Director
and VicePresident
.
Notice that each is a simple data object and has no idea nor reference to the
existence of the other classes.
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.
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 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.
//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
classes 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 that this example implies. For example, the Approve
method could call some function on request or simply be a delegate to another
object.
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 code. For the below
example, all three approval chain depths are seen.
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 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 might stress that making the effort to use patterns will strengthen your overall design ability, but like your basic coding skills, it is something learned 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 first revision and is the fourth installment in a series.
Related Articles
Other articles in this series include: