Saturday, March 6, 2010

Exploring the Chain Of Responsability pattern

The COR pattern is one of the lesser gods in the design pattern universe. It is used when a set of handlers each have a shot at an object (think request), until one of the handlers indicates that it has taken responsibility for that object. Consider a network service that hosts a set of service handlers. A request comes in, and that request is handed down the chain of handlers until it meets all the conditions of that handler. The request is handled by that handler, and is ignored by all the following handlers. Obviously, the position of a given handler, with respect to others in the chain is important. The final handler is often a "Catch-All" to take care of those miscreants that nobody else wants. Examples of implementations the COR pattern include the RADIUS server (Internet Authentication Serer aka IAS) that ships with Windows 2000+. Every handler in that product is a "policy", which is a collection of conditions and a "profile", which is a collection of RADIUS attributes. When all the conditions in a policy are met, the request is handled. In this case, "handled" means that profile is returned to the client. Most profiles include the Access-Accept attribute. The final handler has the "anything" condition,and always returns a profile with an Access-Reject attribute. BTW, IAS has evolved to become the Network Policy Server in W2K8, so if you are wondering why your virus-infested laptop has been quarantined to a remote section of the network with only AV servers available, you have just experienced the power of the COR pattern.

Enough with that, let's implement one! Based on Bizz-Buzz


using System;
using System.Collections.Generic;

namespace FooBarTest // inspired by a coding test on codinghorror
{
public class Program
{
public static void Main(string[] args)
{
// We'll have a COR service that takes a number of returns a string
// There will be 4 policies in it
// the condition delegate indicates what a handled condition is.
ChainOfResponsability<string,int> cor = new ChainOfResponsability<string,int>(4,(s)=>{return (String.Empty != s);});
// The first stage is the "15" handler. Any int that is evenly
// divisible by 15 will return the string "foobar"
// Order is important. You'll want to order your conditions from
// specific to general
cor.AddPolicy(0, (n) => { return (0 == (n % 15)) ? "Bizz Buzz": String.Empty; });
// The order of second and third stage are not important.
cor.AddPolicy(1, (n) => { return (0 == (n % 5)) ? "Buzz" : String.Empty; });
cor.AddPolicy(2, (n) => { return (0 == (n % 3)) ? "Bizz" : String.Empty; });
// The most general condition. The "Catch ALl".
cor.AddPolicy(3, (n) => { return n.ToString(); });
// Look for "Bizz Buzz" when 15 is a root. "Buzz" for 5, and "Bizz" for 3.
for (int i = 0; i < 100; i++) Console.WriteLine(cor.Evaluate(i).result);
Console.ReadLine();
}
}

public class ChainOfResponsability<RESULT,VALUE>
{

public class Tuple // Results, and which policy handled it
{
public RESULT result { get; private set; }
public bool Handled { get; private set; }
public int Stage { get; private set; }
public policy Policy { get; private set; }
}

public Tuple(bool handled, int stage, policy p, RESULT r)
{
this.result = r; // the result of the policy that handled this
this.Handled = handled; // did the request get handled?
this.Stage = stage; // which stage handled it?
this.Policy = p; // which policy handled it?
}
}
public delegate bool EvalTrue(RESULT x); // function to determine that request handled
public delegate RESULT policy(VALUE y); // a policy function. The heart of our COR service.
public ChainOfResponsability(int stages, EvalTrue ev)
{
this.policies = new policy[stages];
this.CC = ev;
}
private EvalTrue CC; // We store the result evaluation function
private policy[] policies; // the set of policies.

// Add a policy to a specific stage in the pipeline.
// Specifying more than one per stage, is unwise. The later one over-writes.
public void AddPolicy(int stage, policy function)
{
if ((0 <= stage) && (this.policies.Length > stage))
this.policies[stage] = function;
else throw new Exception("invalid stage specified"); // TODO throw a more specific Exception
}

// Evaluate the value against each policy in the pipeline until a policy
// indicates that it has handled it.
// Values that aren't handled will result in the Tuple.Handled == false
}

public Tuple Evaluate(VALUE n)
{
bool handled = false;
RESULT result = default(RESULT); // the result of a policy that handles the value
int invokedStage = -1; // which stage handled it?
policy p = default(policy); // which policy handled it?

// go through each policy in our pipeline
for (int stage = 0; (stage < this.policies.Length && !handled); stage++) {
// If there is a policy at this stage, give it a shot at it
if (null != this.policies[stage]) {
result = this.policies[stage].Invoke(n);
handled = this.CC.Invoke(result); // did the current policy handle this?
if (handled) { // yep. let's remember where and who.
invokedStage = stage;
p = this.policies[stage];
}
}
}
return new Tuple(handled,invokedStage,p,result); // immutable
}
}
}

No comments:

Post a Comment