17 March, 2011

How to: Create a simple Chain-Of-Responsibility (COR) implementation

When creating a generic system, that should be able to handle a large (and potentially unknown) number of incoming requests – you can basically go 2 routes:

1) A very large switch-statement
2) Create an COR-chain

Examples of such system needs could be:

1) File Parsing system
2) Http-Request handler implementation
3) Product requests of various kind.

A switch statement very fast proves to be a bad idea. First of all, you need to recompile every time a new request type has arrived. Secondly, it becomes messy and is really bad practice to be honest. It also violates the Separation Of Concern principle that mandates “keep classes focused and do one thing only”.

A COR-chain is a way better approach. It allows you to create focused classes that does one thing only. It makes maintenance and evolution of the system a lot easier. And we all know that systems tend to evolve often sooner than later.

COR-chain principle

So - how do one create a simple implementation of such COR-chain?

The architectural overview is this. The Client only knows of an abstract base class, that is returned to the Client by means of a Factory [ChainFactory] that has the single responsibility of constructing a working chain. The Client only knows what a HandlerBase looks like (see the separation of concern here as well?).

Overall architecture

Implementation
The implementation of the above structure is seen here.

HandlerBase:
The below is the baseclass from which all implementations inherit. This is actually the most important part of the COR-system as it lays down the business logic of the system. All concrete implementations should inherit from this class and implement their own version of the 2 abstract methods.  Also seen - if the current handler is not able to handle the incoming request, it will delegate this to the next in line.

If no  implementation can handle the request, a NotSupportedException is throw by the handler.

/// <summary>
///
Baseclass for handling of file parsing.
/// </summary>
abstract class HandlerBase
{
/// <summary>
///
Next in line...
/// </summary>
protected HandlerBase m_Successor;

/// <summary>
///
Sets the successor.
/// </summary>
/// <param name="successor">
The successor.</param>
public void SetSuccessor(HandlerBase successor)
{
m_Successor = successor;
}

/// <summary>
///
Determines whether this instance can handle the specified file ext.
/// </summary>
/// <param name="fileExt">
The file ext.</param>
/// <returns>
/// <c>
true</c> if this instance can handle the specified file ext; otherwise, <c>false</c>.
/// </returns>
protected abstract bool CanHandle(string fileExt);

/// <summary>
///
Handles the specified file name.
/// </summary>
/// <param name="fileName">
Name of the file.</param>
public void Handle(string fileName)
{
if (CanHandle(fileName)) //can this impl. handle?
{
HandleCore(fileName);
return; //we are done.
}
if (m_Successor != null)
m_Successor.Handle(fileName); //attempt with next in line...
else
throw new
NotSupportedException("unknown fileformat"); //we can not handle!
}

/// <summary>
///
Core implementation (to be impl. in concrete handlers).
/// </summary>
/// <param name="fileName">
Name of the file.</param>
protected abstract void HandleCore(string fileName);
}

ChainFactory
The factory has only one function in this system. It is responsible for creating the chain, link them together and finally returning to the calling client, a reference to the first link in the chain.

/// <summary>
///
ChainFactory
/// </summary>
class ChainFactory
{
/// <summary>
///
Creates the chain.
/// </summary>
/// <returns></returns>
public static HandlerBase CreateChain()
{
var h1 = new TxtHandler(); //first handler
var h2 = new PdfHandler(); //second handler

h1.SetSuccessor(h2); //link togethr

return h1; //return first "link" in chain
}
}

Implementations (Txt, Pdf)
These 2 implementations are made with support for Txt and PDF. Each handler implementations state in the CanHandle-method, whether they can handle the incoming file request. In positive case, they will be handling this and we are done (see abstract HandlerBase).

/// <summary>
///
TxtHandler
/// </summary>
class TxtHandler : HandlerBase
{
protected override bool CanHandle(string fileExt)
{
return fileExt == ".txt";
}

protected override void HandleCore(string fileName)
{
throw new NotImplementedException(); //here should be txt impl.
}
}
/// <summary>
///
PdfHandler
/// </summary>
class PdfHandler : HandlerBase
{
protected override bool CanHandle(string fileExt)
{
return fileExt == ".pdf";
}

protected override void HandleCore(string fileName)
{
throw new NotImplementedException(); //here should be impl.
}
}

Finally, the calling client using this COR-chain is seen here. As seen, this client (called Manager) asks for a Chain from the ChainFactory and next lets the Chain handle the incoming file (fileName).

/// <summary>
///
Manager
/// </summary>
class Manager
{
public void HandleFile(string fileName)
{
//create chain
var ch = ChainFactory.CreateChain();

//throw in file (and let chain handle the reading...)
ch.Handle(fileName);

}
}

With this system, you are able to throw in new implementations (e.g. xml, doc, xls…) without affecting the client. Only the ChainFactory should know about these new implementations. It is very easy to extend and all is well ;-)


Technorati Tags:

2 comments:

Bernoli said...

What is the meaning of large generic systems? Do you suggest that every switch will be translated to COR? and why not to use polymorphizem in some cases that can also solve this problem?

Thanks !

Claus Konrad said...

You are very right. A polymorphism will solve the problem with different incoming file formats as well. Should every switch statement be replaced by COR? In my (by now) vast experience; you often start out with a switch statement, but it very often outgrows you and often very fast. That's why I've come to like COR for this responsbility. In addition, the COR design implemented with MEF (Managed Ext. Framework) allows for a very loosely coupled implementation that is very sweet. You basically just drop a new handler (physically) in the folder of choise and MEF picks it up. You just implement some interface and you are good to go.