September 2, 2014
Hot Topics:
RSS RSS feed Download our iPhone app

Working with Design Patterns: Template Method

  • April 27, 2007
  • By Jeff Langr
  • Send Email »
  • More Articles »

Design patterns are powerful tools. Powerful tools always present the potential for misuse. If we're not careful, we could chop off a leg or glue our code together into a stick mess. Template Method is a design pattern that presents one solution for eliminating common duplication between two or more related classes. It is one of the twenty-three patterns demonstrated in the book Design Patterns (1995, by Gamma, et. al.).

You're best off treating Template Method as a refactoring goal. In this article, you'll step through an example that shows how to manipulate code to meet that goal. Instead of talking about what Template Method even is, we'll just refactor and talk about the results when we're done.

For this example, we're building a rule-based workflow system for managing scanned forms. The first rule we tackle requires that the state supplied on a form match the state passed in as an argument. The class CheckStateRule implements the Rule interface:

import java.util.*;

public interface Rule {
   boolean eval(Case c, List<String> val, boolean returnTrue);
}
The workflow system executes rules. Executing a rule can move forms through the workflow.

The CheckStateRule code is fairly straightforward. The rule engine passes a Case object, a list of arguments, and a negation flag to the eval method. The Case indirectly contains form data. The switchReturnValues method, a slightly confusing bit, flips the result of the rule if someone has configured it in the workflow system to be negated.

import java.util.*;

/**
 *
 * Check if the state the case is located in is equal to argument passed in.
 */
public class CheckStateRule implements Rule {
   private static final String EXCEPT = "Exception: ";
   String errStr;
   boolean hasMissingData;

   public boolean eval(Case c, List<String> val, boolean returnTrue) {
      boolean returnVal = false;
      String argVal = null;

      try {
         if (val == null || val.size() != 1) {
            errStr = "Can't convert value list to a state";
            hasMissingData = true;
            return false;
         } else {
            argVal = (String)val.get(0);
         }

         // get the location object off the form and get the state the
         // case resides in
         Location loc = (Location)c.getFormComposite("location");
         if (loc == null) {
            errStr = "Cannot determine the state this case is located in.";
            hasMissingData = true;
            return false;
         }
         String st = loc.getAttribute("state");
         String state = (st == null ? null : st.toUpperCase());
         if (state == null) {
            errStr = "Cannot determine the state this case is located in.";
            hasMissingData = true;
            return false;
         } else if (state.equals(argVal)) {
            returnVal = true;
         }

         // return value
         List<Object> returns = switchReturnValues(returnTrue, returnVal,
               "Case does not reside in '" + argVal + "'", "Case resides in '"
                     + argVal + "'");
         returnVal = ((Boolean)returns.get(0)).booleanValue();
         errStr = (String)returns.get(1);

      } catch (Exception e) {
         errStr = EXCEPT + e.getMessage();
         hasMissingData = true;
         return false;
      }

      return returnVal;

   }

   public List<Object> switchReturnValues(boolean returnTrue,
         boolean returnVal, String falseMsg, String trueMsg)
         throws Exception {

      if (!returnTrue)
         returnVal = !returnVal;

      String retErrStr = null;
      if (!returnVal) {
         if (returnTrue)
            retErrStr = falseMsg;
         else
            retErrStr = trueMsg;
      }

      ArrayList<Object> returns = new ArrayList<Object>();
      returns.add(new Boolean(returnVal));
      returns.add(retErrStr);
      return returns;
   }

   public boolean isMissingData() {
      return hasMissingData;
   }

   public String getError() {
      return errStr;
   }
}

As soon as we get CheckStateRule working, we begin work on a second rule, CheckDependentsRule. The job of this second rule is to ensure that each dependent specified on the form has both a first and last name.

import java.util.*;

/**
 *
 * Ensure all dependent detail is supplied
 */
public class CheckDependentsRule implements Rule {
   private static final String EXCEPT = "Exception: ";
   private String errorMessage;
   private boolean missingData = false;

   public boolean eval(Case c, List<String> val, boolean returnTrue) {
      boolean returnVal = false;

      try {
         // get the location object off the form and get the state the
         // case resides in
         Subscriber sub = (Subscriber)c.getFormComposite("subscriber");
         if (sub == null) {
            errorMessage = "Unable to obtain subscriber info.";
            missingData = true;
            return false;
         }

         returnVal = true;
         for (int i = 1; i <= Subscriber.DEPENDENT_ROWS; i++) {
            if (!hasCompleteDependentName(sub, i)) {
               returnVal = false;
               break;
            }
         }

         boolean returnVal1 = returnVal;
         if (!returnTrue)
            returnVal1 = !returnVal1;

         String retErrStr = null;
         if (returnVal1 == false) {
            if (returnTrue)
               retErrStr = "Dependent info incomplete";
            else
               retErrStr = "All dependent info provided";
         }

         List<Object> res = new ArrayList<Object>();
         res.add(new Boolean(returnVal1));
         res.add(retErrStr);

         // return value
         List<Object> returns = res;
         returnVal = ((Boolean)returns.get(0)).booleanValue();
         errorMessage = (String)returns.get(1);

      } catch (Exception e) {
         errorMessage = EXCEPT + e.getMessage();

         missingData = true;
         return false;
      }

      return returnVal;

   }

   private boolean hasCompleteDependentName(Subscriber sub, int number) {
      String firstName = sub.getAttribute("dep" + number + "first");
      String lastName = sub.getAttribute("dep" + number + "last");

      if ((!isEmpty(firstName) && isEmpty(lastName)) ||
            (!isEmpty(lastName) && isEmpty(firstName))) {
         errorMessage = "Complete name must be specified for dependent " + number;
         return false;
      }
      return true;
   }

   private boolean isEmpty(String string) {
      return string == null || string.equals("");
   }

   public boolean isMissingData() {
      return missingData;
   }

   public String getError() {
      return errorMessage;
   }
}




Page 1 of 3



Comment and Contribute

 


(Maximum characters: 1200). You have characters left.

 

 


Sitemap | Contact Us

Rocket Fuel