Have you ever seen those seemingly endless if -> else if -> else if -> ... -> else if statements? Sure you have! You have possibly even written one or two, but confessions are not really necessary. In some cases, all we are trying to do is subject an object to a line of inspection and then branch based on those results. This may be fine, but if you find yourself continuously adding new else if items to the end of that statement, there may be another pattern that is more appropriate. Enter the chain-of-responsibility pattern, What is that?
In Object Oriented Design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects. Each processing object contains a set of logic that describes the types of command objects that it can handle, and how to pass off those that it cannot to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain.
- Wikipedia (http://en.wikipedia.org/wiki/Chain-of-responsibility_pattern)
The description sounds somewhat complicated, so a walkthrough can make this a lot easier to understand. Lets start with a simple example. Say we want to have a bunch of independent modules inspect a payload to ensure it contains something. If we exit the end of the chain with no exceptions, it passes; otherwise it fails. The first thing we need to do is define the structure of each link in our chain.
public interface ILink
{
//Holds the next link in the chain
ILink SuccessorLink { get; set; }
void Inspect(object payload);
}
Any module that is a candidate for this chain will need to implement this interface and honor the flow control by passing valid payloads down the chain and short-circuiting others. Beyond that, each module can do whatever they need. Next we need some links that implement our interface. Both of the below sample modules will simply inspect the payload and either pass it down the chain or short-curcuit via exception. In reality, using exceptions is not the most efficiant way to do this but it suites the sample. You can use any number of methods to handle the short-curcuiting process, but not calling the Inspect method is enough to end processing.
public sealed class LinkTypeA : ILink
{
#region ILink Members
public ILink SuccessorLink { get; set; }
public void Inspect(object payload)
{
if (payload.ToString().Contains("A"))
{
if (SuccessorLink != null)
{
SuccessorLink.Inspect(payload);
}
}
else
{
throw new NotSupportedException("The payload is not supported");
}
}
#endregion
}
public sealed class LinkTypeB : ILink
{
#region ILink Members
public ILink SuccessorLink { get; set; }
public void Inspect(object payload)
{
if (payload.ToString().Contains("B"))
{
if (SuccessorLink != null)
{
SuccessorLink.Inspect(payload);
}
}
else
{
throw new NotSupportedException("The payload is not supported");
}
}
#endregion
}
Next we need some infrastructure to build and manage the chain. Normally I like to approach this process using some kind of Factory pattern driven by configuration meta-data. For this sample we will just create a simple chain manager to demonstrate how to build a chain.
public static class ChainManager
{
public static ILink Chain { get; private set; }
public static void AddLinkToChain(ILink link)
{
if (Chain == null)
{
Chain = link;
}
else
{
AppendLinkToChain(link, Chain);
}
}
private static void AppendLinkToChain(ILink link, ILink chain)
{
if (link == null) return;
if (chain.SuccessorLink != null)
{
AppendLinkToChain(link, chain.SuccessorLink);
}
else
{
chain.SuccessorLink = link;
}
}
}
With the ChainManager available, we just need to initialize the chain and then submit a payload for processing:
ILink linkA = new LinkTypeA();
ILink linkB = new LinkTypeB();
ChainManager.AddLinkToChain(linkA);
ChainManager.AddLinkToChain(linkB);
try
{
ChainManager.Chain.Inspect("ABC");
//The payload passed all checks
}
catch (NotSupportedException)
{
//The payload failed a check
}
One of the great things about this pattern is that you can easily add and remove links, and each link can represent a completely seperate area of concern. When building the chain, you can also order items so they execute in the most efficient manner. This involves placing links that are more likely to lead to short-circuiting high in the link chain. Additionally, since all the links are seperate areas of concern, it makes these easy to test in isolation.
Give it a think... Worth considering for some architectures. Enjoy!
Tags: design patterns