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.
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 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.
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:
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)
{
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)
{
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)
{
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:
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);
changeRequest = new ChangeRequest(1,ChangeType.Modify,"This is a change.");
manager.Approve(ref changeRequest);
changeRequest = new ChangeRequest(1,ChangeType.Remove,"This is a change.");
manager.Approve(ref changeRequest);
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.