The Chain of Responsibility pattern's pitfalls and improvements

Don't call the next chain node in your concrete chain class anymore

Recently I wrote two Java programs (for Microsoft Windows OS) that must catch global keyboard events generated by other applications concurrently running on the same desktop. Microsoft provides a way to do that by registering the programs as a global keyboard hook listener. Coding did not take long, but debugging did. The two programs seemed to work fine when tested separately, but failed when tested together. Further tests revealed that when the two programs ran together, the program that launched first was always unable to catch the global key events, but the application launched later worked just fine.

I resolved the mystery after reading the Microsoft documentation. The code that registers the program itself as a hook listener was missing the CallNextHookEx() call required by the hook framework. The documentation reads that each hook listener is added to a hook chain in the order of startup; the last listener started will be on the top. Events are sent to the first listener in the chain. To allow all listeners to receive events, each listener must make the CallNextHookEx() call to relay the events to the listener next to it. If any listener forgets to do so, the subsequent listeners will not get the events; as a result, their designed functions will not work. That was the exact reason why my second program worked but the first didn't!

The mystery was solved, but I was unhappy with the hook framework. First, it requires me to "remember" to insert the CallNextHookEx() method call into my code. Second, my program could disable other programs and vise versa. Why does that happen? Because Microsoft implemented the global hook framework following exactly the classic Chain of Responsibility (CoR) pattern defined by the Gang of Four (GoF).

In this article, I discuss the loophole of the CoR implementation suggested by GoF and propose a solution to it. That may help you avoid the same problem when you create your own CoR framework.

Classic CoR

The classic CoR pattern defined by GoF in Design Patterns:

"Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request. Chain the receiving objects and pass the request along the chain until an object handles it."

Figure 1 illustrates the class diagram.

Figure 1. CoR class diagram

A typical object structure might look like Figure 2.

Figure 2. CoR object structure

From the above illustrations, we can summarize that:

  • Multiple handlers may be able to handle a request
  • Only one handler actually handles the request
  • The requester knows only a reference to one handler
  • The requester doesn't know how many handlers are able to handle its request
  • The requester doesn't know which handler handled its request
  • The requester doesn't have any control over the handlers
  • The handlers could be specified dynamically
  • Changing the handlers list will not affect the requester's code

The code segments below demonstrate the difference between requester code that uses CoR and requester code that doesn't.

Requester code that doesn't use CoR:

      handlers = getHandlers();
      for(int i = 0; i < handlers.length; i++)
      {
         handlers[i].handle(request);
         if(handlers[i].handled())
            break;
      }

Requester code that uses CoR:

      getChain().handle(request);

As of now, all seems perfect. But let's look at the implementation GoF suggests for the classic CoR:

      public class Handler 
      {
         private Handler successor;
         public Handler(HelpHandler s)
         {
             successor = s;
         }
         public handle(ARequest request)
         {
            if (successor != null)
               successor.handle(request);
         }
      }
      public class AHandler extends Handler 
      {
            public handle(ARequest request)
            {
                if(someCondition)
                   //Handling: do something
                else
                   super.handle(request);
            }
       }

The base class has a method, handle(), that calls its successor, the next node in the chain, to handle the request. The subclasses override this method and decide whether to allow the chain to move on. If the node handles the request, the subclass won't call super.handle() that calls the successor, and the chain succeeds and stops. If the node doesn't handle the request, the subclass must call super.handle() to keep the chain rolling, or the chain stops and fails. Because this rule is not enforced in the base class, its compliance is not guaranteed. When developers forget to make the call in subclasses, the chain fails. The fundamental flaw here is that the chain execution decision-making, which is not the business of subclasses, is coupled with request-handling in the subclasses. That violates a principle of object-oriented design: an object should mind only its own business. By letting a subclass make the decision, you introduce extra burden on it and the possibility for error.

Loophole of Microsoft Windows global hook framework and Java servlet filter framework

The implementation of Microsoft Windows global hook framework is the same as the classic CoR implementation suggested by GoF. The framework depends on the individual hook listeners to make the CallNextHookEx() call and relay the event through the chain. It assumes developers will always remember the rule and never forget to make the call. By nature, a global event hook chain is not classic CoR. The event must be delivered to all listeners in the chain, regardless of whether a listener already handles it. So the CallNextHookEx() call seems to be the job of the base class, not the individual listeners. Letting the individual listeners make the call doesn't do any good and introduces the possibility for stopping the chain accidentally.

The Java servlet filter framework makes a similar mistake as the Microsoft Windows global hook. It follows exactly the implementation suggested by GoF. Each filter decides whether to roll or stop the chain by calling or not calling doFilter() on the next filter. The rule is enforced through javax.servlet.Filter#doFilter() documentation:

"4. a) Either invoke the next entity in the chain using the FilterChain object (chain.doFilter()), 4. b) or not pass on the request/response pair to the next entity in the filter chain to block the request processing."

If one filter forgets to make the chain.doFilter() call when it should have, it will disable other filters in the chain. If one filter makes the chain.doFilter() call when it should not have, it will invoke other filters in the chain.

Solution

The rules of a pattern or a framework should be enforced through interfaces, not documentation. Counting on developers to remember the rule doesn't always work. The solution is to decouple the chain execution decision-making and the request-handling by moving the next() call to the base class. Let the base class make the decision, and let subclasses handle the request only. By steering clear of decision-making, subclasses can completely focus on their own business, thus avoiding the mistake described above.

Classic CoR: Send request through the chain until one node handles the request

This is the implementation I suggest for the classic CoR:

    /**
     * Classic CoR, i.e., the request is handled by only one of the handlers in the chain.
     */
    public abstract class ClassicChain
    {
      /**
       * The next node in the chain.
       */
      private ClassicChain next;
      public ClassicChain(ClassicChain nextNode)
      {
         next = nextNode;
      }
      /**
       * Start point of the chain, called by client or pre-node.
       * Call handle() on this node, and decide whether to continue the chain. If the next node is not null and
       * this node did not handle the request, call start() on next node to handle request.
       * @param request the request parameter
       */
      public final void start(ARequest request)
      {
          boolean handledByThisNode = this.handle(request);
          if (next != null && !handledByThisNode)
             next.start(request);
      }
      /**
       * Called by start().
       * @param request the request parameter
       * @return a boolean indicates whether this node handled the request
       */
      protected abstract boolean handle(ARequest request);
    }
    public class AClassicChain extends ClassicChain
    {
       /**
        * Called by start().
        * @param request the request parameter
        * @return a boolean indicates whether this node handled the request
        */
       protected boolean handle(ARequest request)
       {
           boolean handledByThisNode = false;
           if(someCondition)
           {
               //Do handling
               handledByThisNode = true;
           }
           return handledByThisNode;
        }
     }

The implementation decouples the chain execution decision-making logic and request-handling by dividing them into two separate methods. Method start() makes the chain execution decision and handle() handles the request. Method start() is the chain execution's starting point. It calls handle() on this node and decides whether to advance the chain to the next node based on whether this node handles the request and whether a node is next to it. If the current node doesn't handle the request and the next node is not null, the current node's start() method advances the chain by calling start() on the next node or stops the chain by not calling start() on the next node. Method handle() in the base class is declared abstract, providing no default handling logic, which is subclass-specific and has nothing to do with chain execution decision-making. Subclasses override this method and return a Boolean value indicating whether the subclasses handle the request themselves. Note that the Boolean returned by a subclass informs start() in the base class whether the subclass has handled the request, not whether to continue the chain. The decision of whether to continue the chain is completely up to the base class's start() method. The subclasses can't change the logic defined in start() because start() is declared final.

In this implementation, a window of opportunity remains, allowing the subclasses to mess up the chain by returning an unintended Boolean value. However, this design is much better than the old version, because the method signature enforces the value returned by a method; the mistake is caught at compile time. Developers are no longer required to remember to either make the next() call or return a Boolean value in their code.

Non-classic CoR 1: Send request through the chain until one node wants to stop

This type of CoR implementation is a slight variation of the classic CoR pattern. The chain stops not because one node has handled the request, but because one node wants to stop. In that case, the classic CoR implementation also applies here, with a slight conceptual change: the Boolean flag returned by the handle() method doesn't indicate whether the request has been handled. Rather, it tells the base class whether the chain should be stopped. The servlet filter framework fits in this category. Instead of forcing individual filters to call chain.doFilter(), the new implementation forces the individual filter to return a Boolean, which is contracted by the interface, something the developer never forgets or misses.

Non-classic CoR 2: Regardless of request handling, send request to all handlers

For this type of CoR implementation, handle() doesn't need to return the Boolean indicator, because the request is sent to all handlers regardless. This implementation is easier. Because the Microsoft Windows global hook framework by nature belongs to this type of CoR, the following implementation should fix its loophole:

    /**
     * Non-Classic CoR 2, i.e., the request is sent to all handlers regardless of the handling.
     */
    public abstract class NonClassicChain2
    {
        /**
         * The next node in the chain.
         */
        private NonClassicChain2 next;
        public NonClassicChain2(NonClassicChain2 nextNode)
        {
             next = nextNode;
        }
        /**
         * Start point of the chain, called by client or pre-node.
         * Call handle() on this node, then call start() on next node if next node exists.
         * @param request the request parameter
         */
        public final void start(ARequest request)
        {
            this.handle(request);
            if (next != null)
               next.start(request);
        }
        /**
         * Called by start().
         * @param request the request parameter
         */
        protected abstract void handle(ARequest request);
     }
     public class ANonClassicChain2 extends NonClassicChain2
     {
        /**
         * Called by start().
         * @param request the request parameter
         */
        protected void handle(ARequest request)
        {
            //Do handling.
        }
      }

Examples

In this section, I'll show you two chain examples that use the implementation for non-classic CoR 2 described above.

Example 1

This example shows how the second non-classic CoR implementation is used to implement a chain that gathers user group IDs from different sources and by different logics. The request parameters are passed to all handlers in the chain, and each handler gets the group IDs the user belongs to through its own logic. The chain result is designed as the method return, so the client can get the result by making a regular method call. Let's look at the base class first:

        public abstract class GroupChain
        {
          private GroupChain next;
          public void setNext(GroupChain nextG)
          {
            next = nextG;
          }
          public final IntegerSet start(String workid, String[] lobs,String[] grades)
          {
            IntegerSet result = this.execute(workid,lobs,grades);
            if (next != null)
            {
                  IntegerSet nextResult = next.start(workid,lobs,grades);
                  if(nextResult != null)
                        result.add(nextResult);
            }
            
            return result;
          }
          protected abstract IntegerSet execute(String workid, String[] lobs,String[] grades);
         }

The setNext() method, along with the default constructor, is used to make the chain configurable and dynamically loadable. Please read the full source, which can be downloaded from Resources, to see how to do that. The start() and execute() methods return the whole chain's handling result. The client can make a regular method call and get the whole result as follows:

        IntegerSet result = getChain().start(workid,lobs,grades);

The result can also be collected by passing a collection as a request parameter to each handler as follows:

        IntegerSet result = new IntegerSet();
        getChain().start(result,workid,lobs,grades);

This approach is a little awkward and should only be used when the method return is not available (used for other purpose). Note that the data type of the variable that holds the result should be a specific not a generic collection, forcing an individual handler to return or add objects of the same data type. Otherwise different handlers may return or add different data type instances, which may cause the chain to fail at runtime. Of course, you can enforce the rule in documentation, but again, that is bad design. When you enforce data type checking through the interface, no room is left for doubt. In this example, the return type is a user-defined IntegerSet, a set of integers to capture the integer group IDs. Only integers can be returned or added to the collection.

The subclasses are trivial. They simply implement execute(), get the group IDs and return an IntegerSet. See Resources for the source code.

Example 2

This example uses the second non-classic CoR implementation to implement an action chain that allows multiple actions to process an HTTP request under the Struts framework. First we have the chain code:

        public abstract class ActionChain
        {
          private ActionChain next;
          public void setNext(ActionChain nextC)
          {
            next = nextC;
          }
          public final void start(ActionMapping mapping,ActionForm form,
                              HttpServletRequest request,HttpServletResponse response)
            throws Exception
          {
            this.execute(mapping,form,request,response);
            if (next != null)
                   next.start(mapping,form,request,response);
          }
          protected abstract void execute(ActionMapping mapping,ActionForm form,
                                            HttpServletRequest request,HttpServletResponse response)
            throws Exception;
         }
         public class ActionChainNode1 extends ActionChain
         {
           protected void execute(ActionMapping mapping,ActionForm form,
                                   HttpServletRequest request,HttpServletResponse response);
            throws Exception
            {
                  //Process request.
            }
         }

Note that the chain nodes are not Struts actions. They do not return an ActionForward, but do process the request. A dedicated action defined below serves as the client that builds the chain out of configuration, calls the chain to process the request, and returns an ActionForward:

      public ChainAction extends Action
      {
         public ActionForward execute(ActionMapping mapping,ActionForm form,
                                      HttpServletRequest request,HttpServletResponse response)
         throws Exception 
         {
             //Get the chain.
             ActionChain chain = getChain(mapping);
             
             //Start the chain, asking the chain to handle the request.
             chain.start(mapping,form,request,response);
             
             return mapping.findForward("success");
         }

The chain nodes are defined in a Struts configure file as an action's attribute parameter as follows:

   <action   path="/myaction"   parameter="com.xyz.chain.ActionChainNode1,com.xyz.chain.
    ActionChainNode2,com.xyz.chain.ActionChainNode3"> 
      <forward name="success"   path="/jsp/myaction.jsp"/>
   </action>

Conclusion

The buzzwords for software design are "decouple, decouple, and decouple." The fundamental flaw of the classic CoR implementation suggested by GoF is that the chain execution decision-making, which is not the business of subclasses, is coupled with request-handling in the subclasses. That violates a principle of object-oriented design: an object should mind only its own business. The solution is to decouple the chain execution decision-making and the request-handling by moving the next node call to the base class. Let the base class make the decision, and let subclasses handle the request only. By steering clear of chain execution decision-making, subclasses can completely focus on their own business, thus avoiding stopping the chain by accident.

I would like to thank Bobby Paraskevopoulos and Yijun Sun for valuable suggestions in completing this article.

Michael Xinsheng Huang is a Sun Certified Java programmer, Java 2 Developer, and J2EE Architect. Currently, he is working for Edgewater Technology as a senior developer. Huang has been an active proponent of design patterns since 1999. As a leading designer and developer in several large J2EE projects, Huang applies design patterns whenever they are applicable.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies