The Chain of Responsibility pattern's pitfalls and improvements

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

1 2 Page 2
Page 2 of 2

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

This story, "The Chain of Responsibility pattern's pitfalls and improvements" was originally published by JavaWorld.

Copyright © 2004 IDG Communications, Inc.

1 2 Page 2
Page 2 of 2