Introduction
This post is about implementing Chain Of Responsibility design pattern, and few possible extensions to the same. If you are new to design patterns, I suggest you start with Practically Applying Design Patterns – Thought Process.
Coming back to Chain of Responsibility - If you have a scenario where you need to chain multiple handlers to handle an incoming request or command, you better use Chain Of Responsibility.
A typical example is your girlfriend requesting you something – If she is requesting/commanding you something like “Do you want to come with me for my best friend’s Bachelorette party?”, you will handle it directly. But if she is requesting/commanding you some thing like “Buy me a Porsche”, you say “Sorry Honey, I don’t have the money. Better you ask your dad for this, I’ll call him for you.” –i.e, you pass the request to the next handler, in this case your girl friend’s father. To sum up, in the above example, your girl friend is the client who is making the request, and you and your future father-in-law are handlers/approvers who handle/approve her requests. If you cannot handle it, you pass that responsibility to the next handler/approver in the chain.
A Minimal Example
To consider a more formal example, assume a scenario where you’ve a banking system, and you want to implement some kind of Loan approval. The customer may request a loan, and if it is below a specific amount, the cashier may approve it directly. If it is above the specified amount, he might pass the request to his manager for approval.
So you may use Chain Of Responsibility implementation to hand over the request/command to the correct approver. For an example, consider this implementation of the above Bank account scenario. Our business rule is something like, a cashier can approve the request if the amount is lesser than 1000 $$, otherwise the approval should be passed to the manager. The manager can approve the request if the amount is lesser than 10,000 $$.
We have the following components.
LoanRequest
– A concrete request
IRequestHandler
– Abstract request handler implementation
- Concrete handlers like Cashier and Manager implement this
- Has a reference to the successor to pass the request
Program
– The main driver
To the code:
using System;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DesignPatternsCof
{
class LoanRequest
{
public string Customer { get; set; }
public decimal Amount { get; set; }
}
interface IRequestHandler
{
string Name { get; set; }
void HandleRequest(LoanRequest req);
IRequestHandler Successor { get; set; }
}
static class RequestHandlerExtension
{
public static void TrySuccessor(this IRequestHandler current, LoanRequest req)
{
if (current.Successor != null)
{
Console.WriteLine("{0} Can't approve - Passing request to {1}",
current.Name, current.Successor.Name);
current.Successor.HandleRequest(req);
}
else
{
Console.WriteLine("Amount invalid, no approval given");
}
}
}
class Cashier : IRequestHandler
{
public string Name { get; set; }
public void HandleRequest(LoanRequest req)
{
Console.WriteLine("\n----\n{0} $$ Loan Requested by {1}",
req.Amount, req.Customer);
if (req.Amount<1000)
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount,req.Customer, this.Name);
else
this.TrySuccessor(req);
}
public IRequestHandler Successor { get; set; }
}
class Manager : IRequestHandler
{
public string Name { get; set; }
public void HandleRequest(LoanRequest req)
{
if (req.Amount < 10000)
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.Name);
else
this.TrySuccessor(req);
}
public IRequestHandler Successor { get; set; }
}
class Program
{
static void Main(string[] args)
{
var request1 = new LoanRequest() { Amount = 800, Customer = "Jimmy"};
var request2 = new LoanRequest() { Amount = 5000, Customer = "Ben"};
var request3 = new LoanRequest() {Amount = 200000, Customer = "Harry"};
var manager = new Manager() {Name = "Tom, Manager"};
var cashier = new Cashier(){ Name = "Job, Cachier", Successor = manager};
cashier.HandleRequest(request1);
cashier.HandleRequest(request2);
cashier.HandleRequest(request3);
Console.ReadLine();
}
}
}
And this is what you'll see upon execution:
So, you may observe that Loan Requests from different customers are passed to the cashier in the above example, and the cashier in his approve
method passes the request to his successor (i.e., the manager) if the amount is higher than what he can approve. The implementation is pretty minimal, as you can see.
We actually have an Abstract request handler implementation IRequestHandler
and two concrete request handlers, Cashier
and Manager
. Each request handler may hold a reference to the successor. You may see that we are setting the Successor of Cashier
as Manager
, so if the amount requested is beyond a limit, the cashier
may pass it to the manager
for his approval.
Dynamically Injecting Approvers
Now, let us take a step back, and think how to implement this in such a way that the approval pipeline is extensible? As of now, our pipeline has two approvers, cashier
and manager
, and the manager
can approve loans up to 10,000. Tomorrow, the Bank may decide that the General Manager can approve loans above 10,000 – and what you are going to do? Make the changes, recompile the entire application, move it to QA, initiate a full recursion testing, and deploying everything to production? You may leverage a bit of extensibility here, and let us have a look at leveraging MEF (Managed Extensibility Framework) for the same.
I recommend you to go through my introductory posts on MEF if you are not familiar with MEF concepts.
Let us go for a generic implementation, to load and compose the handlers leveraging MEF. Let us generalize the above implementation a bit, and introduce few more general purpose contracts and classes.
IRequest
– This contract should be implemented by all requests.
IRequestHandler
– Same as earlier. Abstract request handler implementation
ExportHandlerAttribute
– A custom attribute to export MEF parts
IRequestHandlerMetadata
– Used internally for storing the successor information as a type
RequestHandlerGateway
– Does the composition, and passes the request to successors in a chained fashion.
To the code:
public interface IRequest { }
public interface IRequestHandler
{
bool HandleRequest(IRequest req);
IRequestHandler Successor { get; set; }
}
[MetadataAttribute]
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false)]
public class ExportHandlerAttribute : ExportAttribute
{
public Type SuccessorOf { get; set; }
public ExportHandlerAttribute()
: base(typeof(IRequestHandler))
{
}
public ExportHandlerAttribute(Type successorOf)
: base(typeof(IRequestHandler))
{
this.SuccessorOf = successorOf;
}
}
public interface IRequestHandlerMetadata
{
Type SuccessorOf { get; }
}
public class RequestHandlerGateway
{
[ImportMany(typeof(IRequestHandler))]
public IEnumerable<Lazy<IRequestHandler,IRequestHandlerMetadata>>
Handlers { get; set; }
private IRequestHandler first = null;
public RequestHandlerGateway()
{
ComposeHandlers();
first = Handlers.First
(handler => handler.Metadata.SuccessorOf == null).Value;
}
void ComposeHandlers()
{
var aggrCatalog = new AggregateCatalog();
var asmCatalog = new AssemblyCatalog(Assembly.GetExecutingAssembly());
aggrCatalog.Catalogs.Add(asmCatalog);
var container = new CompositionContainer(aggrCatalog);
container.ComposeParts(this);
}
bool TryHandle(IRequestHandler handler, IRequest req)
{
var s =
Handlers.FirstOrDefault(
h => h.Metadata.SuccessorOf == handler.GetType());
if (handler.HandleRequest(req))
return true;
else if (s != null)
{
handler.Successor = s.Value;
return TryHandle(handler.Successor, req);
}
else
return false;
}
public bool HandleRequest(IRequest request)
{
return TryHandle(first,request);
}
}
Cool. So we have the basic stuff there, keep that handy. Now, to have a Chain Of responsibility implementation, you can simply create the concrete parts and export the same. We’ve the following concrete parts:
LoanRequest
– A concrete request
Cashier
, Manager
, and GeneralManager
– Concrete request handlers
You may note that now we can chain the handlers using the Meta data. For example, when you export the manager, you can easily specify that Manager
is the successor of Cashier
, to approve the request. Similarly, you can specify General Manager as the successor of the Manager
. The advantage is, you can simply deploy these components in a loosely coupled manager, and pick them up using the DirectoryCatalog
of MEF during recomposition.
public class LoanRequest : IRequest
{
public string Customer { get; set; }
public decimal Amount { get; set; }
}
[ExportHandler]
public class Cashier : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 1000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
[ExportHandler(SuccessorOf = typeof(Cashier))]
public class Manager : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 10000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
[ExportHandler(SuccessorOf = typeof(Manager))]
public class GeneralManager : IRequestHandler
{
public bool HandleRequest(IRequest r)
{
var req = (LoanRequest)r;
if (req.Amount < 100000)
{
Console.WriteLine("{0} $$ Loan approved for {1} - Approved by {2}",
req.Amount, req.Customer, this.GetType().Name);
return true;
}
return false;
}
public IRequestHandler Successor { get; set; }
}
class Program
{
static void Main(string[] args)
{
Console.WriteLine("Enter Loan Amount:");
var amount = decimal.Parse(Console.ReadLine());
var req = new LoanRequest() {Amount = amount, Customer = "Ben"};
var gateway = new RequestHandlerGateway();
if (!gateway.HandleRequest(req))
Console.WriteLine("Oops, too high. Rejected");
Console.ReadLine();
}
}
And this is what you'll get. You can see that the request gets dispatched to the correct handler.
There we go, happy coding with proper decoupling.
History
- 14th November, 2011: Initial post