Click here to Skip to main content
Click here to Skip to main content

Extending Chain of Responsibility Design Pattern

, 20 Aug 2007 CPOL
Rate this:
Please Sign up or sign in to vote.
This article details an extension of the chain of responsibility design pattern. Small class library with UML diagrams and source with a practical example using the library are included.

Prerequisites

To fully understand this article you must be familiar with the Chain Of Responsibility (CoR) design pattern. On this website there are some good articles that can help you. If you are not familiar with CoR please read some of them and then come back here. If you already know what CoR is - please continue.

Refactoring to Patterns: Chain Of Responsibility Pattern

Chain of Responsibility

Introduction

This article discusses an extension of the Chain of Responsibility (CoR) design pattern. The pattern is used to reduce the complexity of some program logic. With ease the client can change the flow of the logic statically or dynamically. He can also add new actions that must be taken. It introduces a way to share code using aggregation and interfaces. The client can also predefine the behavior of handling the specific request.

First I am going to present you with the library itself - it is small and contains just four objects. The UML diagram describes the relationships between them. Then I am going to describe their responsibilities. After that a description of each kind of object follows. There are some "tips and tricks" using the library depending on the variety of requirements you want to satisfy. After that a real world example using the library is present.

Background

I had to write a component for handling customer requests. There were many ifs and switches within the complex logic of handling the request. In some handling cases the same actions had to be executed. The requirements and the structure of the logic were changing often and I had to have flexibility in assigning the required responsibilities and actions.

Using the Code - The Core Library

Screenshot - ExtendingCoR1.gif

In the extension a separation of the objects responsibilities has been made. A Handler object is one that decides if he should handles a request. An Action is an object that does the real job with the request. Every Handler has attached Actions to it. If he has to handle the request - he just passes it to his Actions. If not - it behaves just like the normal Chain of Responsibility - he just passes it to the next Handler in the chain. The behaviour of the library can be very easily changed.

The Actions

If you want an action to be taken when a handler must handle a specific request, you should create a XXXAction class. You should implement the IAction<TContext> interface by writing an imlpementation of Execute method. The TContext in most cases is the request itself. If you want a subscriber to be notified when an action is executed you can inherit from the Action<TContext, TExecutedEventArgs> abstract class. Normally the event should be fired when the action has executed successfully. You can use the protected RaiseExecutedEvent method for raising the event.

public class ActionWithEvent : IAction<string, EventArgs>
{
    public override void Execute(string context)
    {
        try
        {
            // execute the action here
            // raise the event when everything is ok
            RaiseExecutedEvent(this, EventArgs.Empty);
        }
        catch (SomeSpecificException ex)
        {
            // handle the concrete exception
        }
    }
}

The first benefit of using these XXXAction classes is that they can easily be reused in different Handlers. Through the interface you can also easily create adaptors that call your other, already written logic.

The Handlers - Handle Algorithm

When you want to create a handler you can implement the IHandler<TRequest> interface. The prefered way to do it is to inherit from the Handler<TRequest> class. It has written with all common functionality and has provided the client the possibility to change its default behavior. The only necessary method you must implement is MustExecuteHandleActions which in other words means "should I handle the request." The default behaviour of the Handler is:

public virtual void Handle(TRequest request)
{
    bool mustExecuteHandleActions = MustExecuteHandleActions(request);
    if (mustExecuteHandleActions)
        ExecuteHandleActions(request);

    bool mustPassRequestToNext = !mustExecuteHandleActions 
                                 || mAlwaysPassRequestToNextHandler;
    if (mustPassRequestToNext)
        PassRequestToNextHandler(request);
}

The algorithm is simple - if the handler must handle the request then all the actions are executed. If not - the request is passed to the next handler. The way you can use the handler is by calling his Handle method and pass him the request.

SomeHandler handler = new SomeHandler();
// attach actions here
SomeRequest request = new SomeRequest();
handler.Handle(request);

The Handlers - Actions

The Handler object has a collection of IActions. You add them through the AddHandleAction method.

SomeHandler handler = new SomeHandler();
handler.AddHandleAction(new ConcreteAction());

They are executed in the way added in the collection. If you want to change the default behaviour of execution of the actions you should override the ExecuteHandleActions method.

The Handlers - Passing the Request to the Next Handler

The Handler keeps the next Handler which he eventually passes the request to. You can set it through the NextHandler property.

FirstHandler first = new FirstHandler();
SecondHandler second = new SecondHandler();
first.NextHandler = second;

Thus you can construct the chain of Handlers, which, one by one receive the request until one of them (by the default behaviour) handles it. If you want all the Handlers in a concrete chain to receive and try to handle the request you should set their AlwaysPassRequestToNextHandler to true.

FirstHandler first = new FirstHandler();
first.AlwaysPassRequestToNextHandler = true;
SecondHandler second = new SecondHandler();
second.AlwaysPassRequestToNextHandler = true;
ThirdHandler third = new ThirdHandler();

first.NextHandler = second;
second.NextHandler = third;

SomeRequest request = new SomeRequest();
first.Handle(request);

This way all the three Handlers will receive the request.

The Handlers - The Request As a Way The Handlers Communicate

The request itself can be used as a way for Handlers to communicate in some way. You can set some properties of the request which the next Handler can use when it decides if it should handle it. Yes or not, if the AlwaysPassRequestToNextHandler is set to true - the request is passed to the next Handler.

The Handlers - Implementing a Tree Instead of a Chain

Creating a simple chain has some disadvantages. For example, it can become too long, which slows the process. In such a case you can create a tree of Handlers. It is prefered though not to complicate the tree more than just one if-then-else construction because if you place a complex switch inside it, the whole pattern becomes useless, as this is one of the problems it solves. The easiest way to create such a handler is by doing a few little steps. First: most probably you don't want your simple-if-handler to execute some Actions so you override the MustExecuteHandleActions to return false. Second: you can place the logic of passing the request in overriden PassRequestToNextHandlers method. But you must have instantiated the class with two references of Handler objects.

public class IfHandler : Handler<string>
{
    private Case1Handler case1Handler;
    private Case2Handler case2Handler;

    public IfHandler(Case1Handler case1, Case2Handler case2)
    {
        case1Handler = case1;
        case2Handler = case2;
    }
    public override bool MustExecuteHandleActions(string request)
    {
        return false;
    }
    public override void PassRequestToNextHandlers(string request)
    {
        if (request != null)
            case1Handler.Handle(request);
        else
            case2Handler.Handle(request);
    }
}

Overriding MustExecuteHandleActions to return false is not mandatory. If you still want some Actions to be executed you can implement it. But in order to sucessfully pass (if needed of course) the request to one of the next two handlers - you should set the AlwaysPassRequestToNextHandler to true.

Using the Code - Real World Example

Screenshot - ExtendingCoR2.gif

Imagine you have to write a component that handles customer requests. The request is for a current product the customer wants to buy. It arrives and you have to check for its request number. Thus we create an InitialCustomerRequestHandler. If the request number is invalid we have to generate it. Thus we create AssignUniqueGuidAction and assign it to the InitialCustomerRequestHandler. We set the AlwaysPassRequestToNextHandler to true so that in both cases the next Handler receives the request.

Then we have to check for product availability. We will not create a simple handler, but one that doesn't have actions and has an if clause which decides to which of its child handlers to pass the request to. We call it ProductAvailabilityHandler.

If the product is not available we have to report that to someone reponsible and we have to offer the customer some of our new products. Thus we create a UnavailableProductHandler and assign to him two new actions - ReportUnavailableProductAction and OfferCustomerNewProductsAction.

In the case when the product is available we have to check if the customer is new. We create a NewCustomerHandler and assign it PriceOfferAction. We also assign it the already created OfferCustomerNewProductsAction.

If the customer is not new the request is received by the RegularCustomerHandler. It has attached to it the already created PriceOfferAction and OfferCustomerNewProductsAction. It also uses the new PriceReductionAction.

Benefits

The extension inherits all benefits from the original CoR - reduced coupling between the sender of the request and the concrete handler of the request.

It has the flexibility of assigning responsibilities — the chain can be built and manipulated dynamically. The extension provides flexibility in assigning the concrete actions.

The main plus is that we separate the logic of the objects between different types of classes - handlers and actions. The actions can be easily reused.

The concrete handlers can be easily unit tested using mock actions. If the logic was inside the request handler it would be impossible to write the tests.

One benefit of the concrete implementation is that it is small, readable and easily used and configurable.

Drawbacks

Besides the good sides of the extension it still suffers the same problems as original CoR. That is - a request is not guaranteed to be handled.

There are also problems such as hardened debugging of the chain of handlers and the execution of the concrete action.

Another drawback is the complicated creation and configuration of the chain.

History

Version 1.0 sent on 16.08.2007.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

Share

About the Author

Cvetomir Todorov
Software Developer
Bulgaria Bulgaria
Cvetomir Todorov is a student in the Sofia University. His interests include Object Oriented Programming and Design, Design Patterns, Producing High-Quality Code, Refactoring, Unit Testing, Test Driven Development etc. The technology he's using is .NET with C# language.

Comments and Discussions

 
GeneralGood one. Pinmemberccpptrain29-Sep-10 19:21 
GeneralGeneric, ready-to-use CoR implementation for .NET and Mono. PinmemberMember 160075526-Aug-08 2:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Terms of Use | Mobile
Web02 | 2.8.1411023.1 | Last Updated 20 Aug 2007
Article Copyright 2007 by Cvetomir Todorov
Everything else Copyright © CodeProject, 1999-2014
Layout: fixed | fluid