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.

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more