Introduction
This article demonstrates how the usage of Chain of Command Design Pattern concepts can be used in the validation and processing of complex data.
Background
The Chain Of Command Design pattern is well documented, and has been successfully used in many software solutions.
In brief, this pattern involves a sequence of loosely coupled programming units, or handler objects. These objects are coupled together to form the links in a chain of handlers.
Each handler performs its processing logic, then potentially passes the processing request onto the next link (i.e. handler) in the chain. A client using the chain will only make one request for processing. After this request, the chain handlers work to do the processing.
Benefits of the Chain Of Command Design Pattern
- Complex processing algorithms can be simplified by breaking down logical units of work, and placing each unit of work in a chain handler.
- Clients are de-coupled from the chain mechanism. Clients using the chain do not have to know specifics of handler processing logic, the structure of the links that make up the chain, nor the individual programming unit that will handle a request.
- Handlers are isolated processing units and are loosely coupled, meaning that one handler does not have knowledge of other handler logic.
- Chains can be dynamically constructed and modified to meet varying requirements.
Notes on Traditional Implementation of the Pattern
- Only one handler in the chain handles a request.
- Some requests may not get handled by any handler in the chain.
- Many handlers are implemented in a linked list fashion, where one handler which has not handled a request, chooses to invoke the next handler in the chain.
Traditional UML Diagram of the Chain Of Command Design Pattern Objects
Introduction to the Example
The example code presents simplified examples of using the chain of command pattern concepts to perform validation on, and processing of a set of data. The techniques can then be applied to real-life, complex requirements. Presented here are the requirements for our three sample problems: (1) Validating login data (user name/password), (2) Validating bank transaction data, and (3) Processing a bank transaction (a deposit or withdrawal).
Requirements for Authorizing a Login
- The login authorization process should STOP when the first error is encountered, and not keep processing the rest of the requirements.
- An integer login id must not be less than or equal to zero.
- The integer login id must be found in the data store.
Requirements for Validating a Bank Account Transaction
- The transaction validation process should examine all data fields of a transaction, regardless of errors in other data fields.
- A transaction string 'account number' must be found in the data store.
- A transaction 'type' single character must be either a 'D' character for a deposit transaction, or a 'W' character for a withdrawal transaction.
- A transaction floating point 'amount' must be a positive value (i.e. greater than zero).
Requirements for Processing A Bank Transaction
- A transaction with a type 'D' and a positive amount will be processed as a deposit
- A transaction with a type 'W' and a positive amount will be processed as a withdrawal.
Variations on the Chain Of Command Theme Used for this Example
This implementation of the chain deviates from the classical Chain of Command Design pattern, and does not attempt to strictly adhere to the classic pattern description. Even though some may disagree, there are no rules about how a pattern should or should not be applied or changed. We are free to modify them to suit our requirements!
Pieces of the Puzzle in the Implementation of the Chain Mechanism
- Each handler is an object that implements an
IChainHandler
interface. - Each handler can optionally throw a
ChainHandlerException
, which indicates some exception condition during processing. - Each handler returns a
HandlerResult
: either a HandlerResult.CHAIN_DATA_HANDLED
, indicating the request was handled and no further processing required, or a HandlerResult.CHAIN_DATA_NOT_HANDLED
, indicating the request was NOT handled, and the request should be passed to the next handler in the chain. - Instead of implementing a chain as a linked list, the actual chain is a
List
of handlers maintained by a ChainManager
class. The manager iterates over this list, invoking each handler's ProcessRequest()
method. - To process a request, the
ChainManager.ProcessRequest()
method is called. - Processing the chain can include the following variations: (1) Pass request data to all handlers, regardless of the HandlerResult returned by each handler, (2) Stop when first handler indicates that it has handled the request successfully, (3) Stop when a handler throws an exception, or (4) Collect all exceptions into a list and pass back that list to the client for analysis.
Two variables passed into the ChainManager
constructor, which are exposed as read only properties, affect the behavior of chain processing:
StopOnFirstException
: Indicates whether the manager should stop the chain processing based on if an exception is thrown by the current handler being processed, or continue down the chain, allowing all handlers to process data regardless of any exceptions that have occurred. ProcessEntireChain
: If true, this flag indicates all handlers in the chain should process the request, regardless of the HandlerResult value returned by the individual handlers. This setting overrides the individual HandlerResult returned values.
Considerations of constructing chains
While attempting to create a flexible chain system, some confusion may occur over the handler return values as they relate the two behavior variables mentioned above.
The meaning of the handler return values, exception lists, are left up to the developer. The flexibility allows one to develop different kinds of chains with varying behaviors.
Some questions to consider when constructing the chain:
- Should only one or all handlers process the clients request?
- Will the handlers produce meaningful exception information to be used by the client? (i.e. the list of exceptions set by the manager's
ChainManager.ProcessRequest()
method) - Should chain processing stop on the first exception, or is it desired to accumulate a list of all exceptions for later analysis?
VS2008 Diagram
The following Visual Studio 2008 class diagram graphically shows all the items involved in the implementation of this example chain system:
Using the Code: Constructing the Login Validation Chain
The login validation chain is constructed in the method, MainForm.ConstructLoginValidationChain()
:
bool stopOnFirstHandlerException;
bool processEntireChain;
stopOnFirstHandlerException = true;
processEntireChain = false;
loginAuthorizationChain = new ChainManager < LoginData >(stopOnFirstHandlerException,
processEntireChain);
loginAuthorizationChain.AppendHandlerToChain(new LoginIdZeroOrNegativeValidationHandler());
loginAuthorizationChain.AppendHandlerToChain(new LoginIdValidationHandler());
loginAuthorizationChain.AppendHandlerToChain(new LoginPasswordValidationHandler());
The client request for chain processing, and the analysis of the returned exceptions are performed in MainForm.btnValidateLoginData_Click()
LoginData loginData = CreateLoginDataFromScreen();
List < ChainHandlerException > validationExceptions;
chainProcessingResult = loginAuthorizationChain.ProcessRequest(loginData,
out validationExceptions);
if (validationExceptions.Count > 0)
else
Note that ChainManager
accepts one generic argument, LoginData
. This is the object type that is passed into the ChainManager
's ProcessRequest()
method, and also the type that is accepted by all of the chain handlers in this chain. Generics were used to provide type safety for the chain manager and the managed handlers.
Diagram of Login Validation Chain And Client Request
See the corresponding method MainForm.ConstructAccountDataValidationChain()
and btnValidateTransaction_Click()
for the construction and usage of the account transaction validation chain.
Note the example of processing transaction data uses the more traditional chain of command mechanism, with only one handler processing the request. See MainForm.ConstructTransactionProcessingChain()
for the construction of the chain and required behavior, and MainForm.btnProcessTransaction_Click()
Points of Interest - Generics For Type Safety
Generics are used in the ChainManager
class and the IChainHandler
interface to provide type safety of the request data. Below are excerpts showing how the single generic argument is used:
Chain management class declaration
public class ChainManager < HANDLER_REQUEST_TYPE >
{
private List< IChainHandler < HANDLER_REQUEST_TYPE > > handlerChain =
new List < IChainHandler < HANDLER_REQUEST_TYPE > >();
public HandlerResult ProcessRequest(HANDLER_REQUEST_TYPE requestData,
out List < ChainHandlerException > chainHandlerExceptionList )
{ ... }
}
Chain Handler Interface Declaration
public interface IChainHandler < HANDLER_REQUEST_TYPE >
{
HandlerResult ProcessRequest(HANDLER_REQUEST_TYPE requestData);
}
Example declaration of a chain handler
public class LoginIdValidationHandler : IChainHandler < LoginData >
{
public HandlerResult ProcessRequest(LoginData requestData) { ...}
}
Conclusion
The Chain of Command design pattern uses relatively simple, loosley coupled, isolated programming units (i.e. chain handlers) linked together to form a chain. The client makes one request to the chain for processing, and has no knowledge of internal chain structure. Chains can be dynamically allocated and modified.
Use this design pattern to break down and solve complicated data processing tasks, which will increase maintainability and flexibility, while reducing the complexity of software solutions.
History
August 10, 2009 Initial creation of example project.