Disciplined Agile

The Chain Of Responsibility Pattern

Contents


The Chain Of Responsibility Pattern


The Chain of Responsibility Pattern

Scott Bain discusses the essential features and the common uses of the Decorator Pattern and the Chain of Responsibility Pattern. Scott is a senior Disciplined Agile technical trainer and thought leader. This is part of a series of recorded presentations brought to you by PMI Disciplined Agile.

Contextual Forces


Motivation

Where there are a number of different entities that handle the same request, and where only one of them will be the correct entity in a given circumstance, we can decouple the client (requester) from the entity that will handle the request in a given case.

Encapsulation

The client cannot see:

  • How many different entities there are that may handle the request (cardinality)
  • Which entity actually handles any given request (variation)
  • The order that the entities are given their chance (sequence)
  • How the selection is achieved (selection)

These issues are all encapsulated by the pattern, especially if the chain itself is created in an object factory.

Procedural Analog

Each entity will be given 'a chance' to handle the request until one of them elects to handle it. Once the request is handled, no further entities are given a chance to handle it. Any entity will self-select (or not), and therefore the selection is done from the point of view of the entity.

A clear procedural analog to this is the switch/case statement or other multi-part branching logic, where the issue that determines the proper branch is relevant to the algorithm itself in each case, rather than a client issue (see Strategy):

switch(condition) {

     case A:
     // Behavior A

     case B
     // Behavior B

     case C:
     // Behavior C

     default:
     // Default Behavior
}

Non-Software Analog

Imagine you had a large number of coins that you need to sort. A simple (old-fashioned) way of doing this is to create a stack of trays, each one with holes in it that match the size of a given coin (one for half-dollars, one for quarters, one for nickels, etc…).

In this stack, the tray with the largest holes will be on top, the next largest will be underneath that, and so on until you have a tray with no holes at the bottom. Only the dimes (which have the smallest diameter) will fall all the way to the bottom tray. The pennies (next smallest) will be caught one tray up, and so forth.

Chain of Responsibility Anology

 

Implementation Forces


Example

Chain of Responsibility 1

...Pseudo Code

public abstract class TargetAbstraction {
     private TargetAbstraction myTrailer;
     public TargetAbstraction(TargetAbstraction aTrailer) {
          myTrailer = aTrailer;
     }
     public ret m(par p) {
          if (isMyAction(p)) {
                return myAction(p);
          }
          if(myTrailer != null {
                return myTrailer.m(p);
          }
          throw new EndOfChainReachedException();
     }
     protected abstract bool isMyAction(par p);
     protected abstract ret myAction(par p);
}

public class Handler1 extends TargetAbstraction {
     public Handler1(TargetAbstraction aTrailer) {
           superclassConstructor(aTrailer);
     }
     protected bool isMyAction(par p) {
           // Decision logic to select Handler 1
     }
     protected ret myAction(par p) {
           // Handler 1 behavior
           return ret;
     }
}

public class Handler2 extends TargetAbstraction {
     public Handler2(TargetAbstraction aTrailer) {
           superclassConstructor(aTrailer);
     }
     protected bool isMyAction(par p) {
           // Decision logic to select Handler 2
     }
     protected ret myAction(par p) {
           // Handler 2 behavior
           return ret;
     }
}

public class Handler3 extends TargetAbstraction {
     public Handler3(TargetAbstraction aTrailer) {
           superclassConstructor(aTrailer);
     }
     protected bool isMyAction(par p) {
           // Decision logic to select Handler 3
     }
     protected ret myAction(par p) {
           // Handler 3 behavior
           return ret;
     }}

Questions, concerns, credibility checks

  • All handlers must have a common interface. How difficult will this be to achieve without sacrificing any key distinction in one or more of the handlers?
  • Is there significance in the order of the chain?
  • Will different chain sequences affect performance in beneficial or harmful ways?
  • How many handlers are there, as a performance issue?
  • What should happen if none of the handlers elects? Is there a default case? Should an exception be thrown (as shown in the code example)?

Options in implementation

As with the Decorator, the Chain of Responsibility can be implemented with any desired collection. The advantage of the linked list is that it entirely encapsulated the handlers, and the selection of the correct handler under a given circumstance. However, a manager can be added to any collection in order to hide these issues.

Thus, a Chain of Responsibility can be implemented in an array, ArrayList, Vector, or any desired collection. The Chain of Responsibility is also a good example of the ability of Object-Oriented designs to replace procedural logic with structures of objects. See “Chain of Responsibility: The Poker Example” below for more details on this.

Chain of Responsibilty can also be implemented using constrained generics. See: CoR in Generics.

 

Consequent Forces


Testing issues

At our blog, Sustainable Test-Driven Development, we've engaged a much more thorough investigation of the CoR and testing.

Here is part one: Testing the Chain Itself

Here is part two: Testing the Chain Factory

Here is what we used to say:

Testing each Chain implementation is simply a matter of giving it the right and wrong state, to see if it responds by returning a correct value, or delegating to the next implementation. In order to test this second issue, a Mock object should be used.

Chain of Responsibility 3

The Mock will serve two purposes:

  • To ensure that the mock does not get called when the Handler is given data is should rightly work on. Also, the return from the Handler is testable
  • To ensure that the mock does get called when the Handler is given data is should not rightly work on. Also, the mock can return a known, and the test can ensure that the Handler does not modify it.

Because of this, an inspectable mock is necessary to facilitate the first point, but the mock must also be conditionable to allow the test to supply it's return in the second point. For details on inspectable and conditionable mocks, see The Mock Object Pattern

Cost-Benefit (gain-loss)

  • The Client is freed from containing the selection logic, strengthening its cohesion.
  • The selection logic is spread out over many objects, and is therefore hard for the developer to see.
  • Business logic regarding ordering dependencies are captured in the ordering of the chain, rather than expressed in code (see "The Poker Example" below)
  • If the chain is added to, it may get lengthy, and may introduce performance problems.

 

Chain of Responsibility: The Poker Example


The Chain of Responsibility gives us an opportunity to demonstrate how various, sometimes surprising issues can be composed into objects, rather than rendered into procedural code. This is not to say that all issues that were once dealt with procedurally should now be deal with in an object-oriented way, but rather that it is helpful to know that such options exist, to allow the development team to make the most advantageous decision.

Our example here is the game of poker. If we were creating a software version of this game, one issue that would have to be dealt with is the "what beats what" rules of poker.

In poker, the hand with the highest card wins, unless one hand has a pair. The highest pair wins unless one hand has two pair. The hand with the highest “higher pair”wins unless one hand has three of a kind. Three of a kind is beaten by a straight (5 cards in sequence, like 5,6,7,8,9). A straight is beaten by a flush (5 cards in the same suit). A flush is beaten by a full house (three of one kind, a pair of another). A full house is beaten by 4 of a kind. 4 of a kind is beaten by a straight-flush (5 cards in sequence and all of the same suit), and the highest hand is a royal flush (the ace-high version of the straight flush).

These are business rules, essentially. Imagine we decided to assign each of these hand types a numeric value, so that once we determined the value of two or more hands, we could easily determine the winner, second place, and so forth. The Chain of Responsibility could be used to capture the rules that bound each hand type to a specific numeric value:

Chain of Responsibility 2

The Hand would send an array of its 5 cards to the first "evaluator" in the chain. Of course, we'd likely build this chain in an object factory, and so the hand would not "see" anything more than a single service object.

The Royal Flush Eval object would look at these 5 cards, and determine, yes or no, if they qualified as a "royal flush". If the cards did qualify, the Royal Flush Eval object would return the numeric value we have assigned to Royal Flushes. If the cards did not qualify, then the Royal Flush Eval would delegate to the next object in the chain, and wait for a result, which it would then return to the Hand.

This delegation would continue until one of the Eval objects self-elected, and then the value would return up the chain and back to the Hand. Note that every possible poker hand has some card which is highest, and so we have a default condition we can use to end the chain, the High Card Eval.

The key point here is this: A Royal Flush is also a Flush. It is also a Straight. If we handed 5 cards which were in fact a Royal Flush to the Straight Eval class, it would respond in the positive: "yes, that's a Straight". 3 of a Kind is also a pair, a Full House is also 3 of a Kind, and so forth.

There is a business rule about this: if you have a Royal Flush, no one can call your hand a Straight, even though this is technically true. There is an ordering dependency about these rules… once you determine that a hand is a Full House, you do not ask if it is a Pair.

This design captures this set of dependencies, but not in conditional code. It is captured simply in the order of the evaluators in the chain, and can therefore be maintained simply by changing that order (there are many variations on Poker, as you may know).

This is one example of how sometime suprising things, in this case business rules, can be modeled in an object-oriented, strongly-encapsulated way, as opposed to encoding them into procedural logic.

We must emphasize here that the point is not to say that all procedural logic should be converted to object structures. This would create overly-complex designs, would tend to degrade performance, and would challenge many developers who tried to understand the design. However, it is important to know that object structures are a possible solution to issues that we would normally think of procedurally, to enable us to make informed decisions.

The advantages to using an object structure include testability, encapsulation, and open-closed-ness.


Generics Implementation


interface CoR
{
    public string operation(string data);
}

class ChainA<T> : CoR where T : class , CoR
{
    protected CoR myNextCoR;
    public string operation(string data)
    {
        if (isMyOperation(data))
        {
            return myOperation(data);
        }
        if (T != null) return myNextCoR.operation(data);
        return "no operation found";

    }
     protected override bool isMyOperation(string data)
     {
        return data.StartsWith("1");
    }
    protected override string myOperation(string data)
    {
        return data.ToUpper();
    }
}

class ChainB<T> : CoR where T : CoR, class
{
    protected CoR myNextCoR;
    public string operation(string data)
    {
        if (isMyOperation(data))
        {
            return myOperation(data);
        }
        if (T != null) return myNextCoR.operation(data);
        return "no operation found";

    }
     protected override bool isMyOperation(string data)
     {
        return data.StartsWith(“2");
    }
    protected override string myOperation(string data)
    {
        return data.ToLower();
    }
}

class CorFactory
{
    public static CoR makeCoR()
    {
        return  new ChainA<ChainB<ChainEnd>>(
            new ChainB<null>());
    }
}