Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page



The Template Design Pattern
Peter RosePeter Rose - 01/2005


ABSTRACT

SUMMARY

HOW THE PATTERN WORKS

SETTING THE STAGE

FOUNDATION OF THE TEMPLATE DESIGN PATTERN

THE DOSHIPPRODUCT() CONTROLLER METHOD

METHOD OVERRIDING IN A CONCRETE DESCENDANT CLASS

STUB METHODS IN ANCESTOR CLASS

PUTTING IT ALL TOGETHER

APPENDIX: SAMPLE PROGRAM OUTPUT

APPENDIX: FULL CODE




Abstract
The Template Design Pattern is used to carry out a predetermined set of processing steps which a group of objects share, but for which each object may or may not need to execute each of those steps.

In other words, there is enough commonality of processing that all objects can implement the same interface and extend from the same ancestor base class, but they don't necessarily have to perform all of the business rule methods required in the interface.

How The Pattern Works
The ancestor base class implements all methods in the interface either concretely or simply as an empty stub. This base class contains a concrete main driver method which calls all of the methods in the order that they need to be called in.

Some methods can be concrete in the abstract base class, while others can just be empty stubs satisfying the requirements of the interface. Each extended descendant of this class can then implement only those methods it needs. Thus, if a method is called that is not implemented on the descendant, the ancestor's empty stub implementation of the method will be called.

The example will show how to use the Template Design Pattern in the evaluation of shipping rules to apply to an order for either domestic or international product purchases. The shipping rules that all orders must be put through - whether they implement that rule or not - are defined in a ShippingRulesInterface class.

The abstract class ShippingRules has a concrete static doShipProduct() method that a user calls. doShipProduct() makes calls to all of the rules about shipping an order. If a rule applies to a particular kind of order, then the concrete descendant of the ShippingRules class will override that method and provide specific functionality for that method.

For example, an order passed to the ShippingRulesDomestic class would not need to execute the addInternationalShippingFee() method and so this method is not found in the ShippingRulesDomestic class. When doShipProduct() calls this method, the empty stub method on the abstract class ShippingRules is called (and thus nothing is done).

The Template Design Pattern is controlled in the doShipProduct() method. This sets out a list of methods that will be called regardless of if the rule is applicable to the order or not. If the rule applies, a concrete implementation on that ShippingRules descendant class overrides the stub on the abstract ShippingRules ancestor and acts on the order. If the rule does not apply, the rule is not overridden on the descendant and thus the ShippingRules ancestor class's empty stub method is called which effectively does nothing to the order.

Setting The Stage
We want to prepare an order object (OrderType) for shipping. Each order is marked as being a domestic or an international order. This determines which shipping rules to implement against that order.


// ************************************************************************
class OrderType {
	private boolean isDomestic = true;
	public String getCountryOrderGoesTo() {return("MyCountryTisOfThee");}
	public boolean isDomesticOrder() {return(isDomestic);}
	public void setDomestic(boolean aIsDomestic) {isDomestic = aIsDomestic;}
} //endclass: OrderType

There is a ShippingRulesInterface class that identifies all of the rules that an order must be exposed to. It is as follows:

// ************************************************************************
interface ShippingRulesInterface {
	public boolean okToShipToCountry(OrderType order);
	public OrderType addInternationalShippingFee(OrderType order);
	public OrderType addSalesTax(OrderType order);
	public OrderType addShippingFee(OrderType order);
} //endclass: ShippingRulesInterface

Foundation of the Template Design Pattern
The Template Design Pattern is foundationed on a main calling or controlling method that sets out a list of other methods to call in a specific order that carry out the business rules of the pattern. In this case, the business rules are those as listed in the ShippingRulesInterface class which must be implemented by each or the classes containing the different shipping rules.

In our example application, there is an abstract ancestor ShippingRules class that implements the ShippingRulesInterface. Extended from ShippingRules are two concrete descendant classes: ShippingRulesDomestic and ShippingRulesInternational.

A simple UML of this relationship is as follows:

diagram

The doShipProduct() Controller Method
ShippingRules contains the controlling method: doShipProduct(). This method calls all methods identified in the ShippingRulesInterface, passing the OrderType order object into the appropriate ShippingRules concrete descendant class. Here is a fragment of this method eliminating unnecessary clutter to show just the Template Design Pattern's calling stack:


	// ==============================================================
	public static OrderType doShipProduct(OrderType order) {
		< ---- housekeeping code would go here ---- >
		if( rules.okToShipToCountry(order) ) {
			order = rules.addInternationalShippingFee(order);
			order = rules.addSalesTax(order);
			order = rules.addShippingFee(order);
		} //endif
		return( order );
	} //endmethod doShipProduct

Here is the complete method showing use of the Command Design Pattern to determine which concrete ShippingRules rules class to implement:

	// ==============================================================
	public static OrderType doShipProduct(OrderType order) {
		ShippingRules rules = null;
		//-- Command Pattern to determine type of rule object to use
		if( order.isDomesticOrder() ) {
			rules = new ShippingRulesDomestic();
		} else {
			rules = new ShippingRulesInternational();
		}

		if( rules.okToShipToCountry(order) ) {
			order = rules.addInternationalShippingFee(order);
			order = rules.addSalesTax(order);
			order = rules.addShippingFee(order);
		} //endif
		return( order );
	} //endmethod doShipProduct

Method Overriding In A Concrete Descendant Class
Each concrete class will only provide overridden methods for the rules that are applicable for it. For example, we can assume that all domestic orders must implement rules about sales tax and shipping fees, but not calculating international shipping fees. Thus, the implementation of the ShippingRulesDomestic class would be as follows:


// ************************************************************************
class ShippingRulesDomestic extends ShippingRules  {
	// ==============================================================
	public OrderType addSalesTax(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesDomestic.addSalesTax().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addSalesTax

	// ==============================================================
	public OrderType addShippingFee(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesDomestic.addShippingFee().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addShippingFee
} //endclass: ShippingRulesDomestic

On the other hand, international shipping rules would not need these rules to be implemented; only the international shipping rules. Thus, the ShippingRulesInternational class would look like this:

// ************************************************************************
class ShippingRulesInternational extends ShippingRules  {
	// ==============================================================
	public OrderType addInternationalShippingFee(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesInternational. " +
		"addInternationalShippingFee().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addInternationalShippingFee
} //endclass: ShippingRulesInternational

Stub Methods In Ancestor Class
Since the ancestor class ShippingRules implements the ShippingRulesInterface, it must provide a body for each defined method. Now, whether any code is executed in those methods is dependant upon whether the method implements a business rule that all descendant concrete classes use in the same manner. For example, note in the ShippingRulesInterface class the method signature for okToShipToCountry().

The okToShipToCountry() method sets forth common rules as to which countries the company can ship orders to. It acts as a check on the order takers who may have written up an order but the content of that order may not be shipped to the indicated country. Code in this method may look something like this, for example:


	// ==============================================================
	public boolean okToShipToCountry(OrderType order) {
		boolean isOkToShip = true;
		String country = order.getCountryOrderGoesTo();
		if( CountrySecurityMgr.okToShipToCountry(country) ) {
		 		isOkToShip = true;
		} else {
		 		isOkToShip = false;
		}
		return( isOkToShip );
	} //endmethod okToShipToCountry

Because the rules in this method are common to all ShippingRules objects and all OrderType objects, the correct location for the concrete implementation of this method is on the abstract ShippingRules class.

In addition to the concrete implementation of the okToShipToCountry() method, the abstract ShippingRules class must provide stub methods for all other ShippingRulesInterface methods. These methods are not common to all ShippingRules descendant objects. Thus, in ShippingRules we define these method stubs in the following way:


	// ==============================================================
	public OrderType addInternationalShippingFee(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addInternationalShippingFee " +
		"does nothing.");
		return( order );
	} //endmethod addInternationalShippingFee

	// ==============================================================
	public OrderType addSalesTax(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addSalesTax does nothing.");
		return( order );
	} //endmethod addSalesTax

	// ==============================================================
	public OrderType addShippingFee(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addShippingFee does nothing.");
		return( order );
	} //endmethod addShippingFee

Thus, even though the doShipProduct() method will call these methods for each ShippingRules object, if the rule is not applicable to that object the stub method on ShippingRules will be executed. As you can see, it does nothing to the order object; it just returns it. Nothing is done.

Putting It All Together
Ok, so how do you actually use the Template Design Pattern? Let's define a TemplateExample.java class to show this.


// ************************************************************************
public class TemplateExample {
	public static void main(String[] args) {
		TemplateExample.log(1,
		"Begin Template Design Pattern example.");

		OrderType newOrder = null;

		TemplateExample.log(1,
		"Sending doShipProduct() a domestic order.");
		newOrder = new OrderType();
		newOrder.setDomestic(true);
		newOrder = ShippingRules.doShipProduct(newOrder);

		TemplateExample.log(1,
		"Sending doShipProduct() an international order.");
		newOrder = new OrderType();
		newOrder.setDomestic(false);
		newOrder = ShippingRules.doShipProduct(newOrder);

		TemplateExample.log(1,
			"End Template Design Pattern example.");
	} //endmain
} //endclass: TemplateExample

As I stated at the outset, each OrderType object will be marked (in some way; I just use a boolean) to indicate whether it is a domestic order or not, i.e. the assignment of the order.setDomestic() method. First, we'll create a domestic order and pass that to the ShippingRules.doShipProduct( newOrder ) static method. Then we want to see what happens when we assign the order to be an international order and pass that into doShipProduct().

We have seen that the doShipProduct() method determines if the order is a domestic or an international order. It then instantiates the appropriate ShippingRules object using the Command Design Pattern. It then passes the order object into each polymorphic method of the ShippingRulesInterface. The end result is that the order object has been modified only when appropriate.

I have provided the full text of the classes with print statements so that you can see the path each order takes as it is evaluated through the Template Design Pattern. A sample output of this is given in the next section.

APPENDIX: Sample Program Output
The following is a sample run of the TemplateExample.java file


   ---> Begin Template Design Pattern example.
   ---> Sending doShipProduct() a domestic order.
     ---> Implementing ShippingRules.okToShipToCountry().
       ---> Stub ShippingRules.addInternationalShippingFee does nothing.
     ---> Implementing ShippingRulesDomestic.addSalesTax().
     ---> Implementing ShippingRulesDomestic.addShippingFee().
   ---> Sending doShipProduct() an international order.
     ---> Implementing ShippingRules.okToShipToCountry().
     ---> Implementing ShippingRulesInternational.addInternationalShippingFee().
       ---> Stub ShippingRules.addSalesTax does nothing.
       ---> Stub ShippingRules.addShippingFee does nothing.
   ---> End Template Design Pattern example.


APPENDIX: Full Code


// TemplateExample.java

// ************************************************************************
/**
 * TemplateExample.java
 *
 * Provides a simple Template Design Pattern Example.
 *
 * The example will show how to use the Template Design Pattern
 * in the evaluation of shipping rules to apply to an order for either
 * international or domestic product purchases.
 *
 * The abstract class ShippingRules has a concrete static doShipProduct()
 * method that a user calls. doShipProduct() makes calls to all of the rules
 * about shipping an order. If a rule applies to a particular kind of order,
 * then the concrete descendant of the ShippingRules class will override that
 * method and provide specific functionality for that method.
 *
 * For example, an order passed to the ShippingRulesDomestic class would
 * not need to execute the addInternationalShippingFee() method and so this
 * method is not found in the ShippingRulesDomestic class. When doShipProduct()
 * calls this method, the empty stub method on the abstract class ShippingRules
 * is called (and thus nothing is done).
 *
 * The Template Design Pattern is controlled in the doShipProduct() method.
 * This sets out a list of methods that will be called regardless of if the
 * rule is applicable to the order or not. If the rule applies, a concrete
 * implementation on that ShippingRules descendant class overrides the stub
 * on the abstract ShippingRules ancestor and acts on the order. If the rule
 * does not apply, the rule is not overridden on the descendant and thus the
 * ShippingRules ancestor class's empty stub method is called which effectively
 * does nothing to the order.
*/
// ************************************************************************
public class TemplateExample {
	public static void main(String[] args) {
		TemplateExample.log(1,
		"Begin Template Design Pattern example.");

		OrderType newOrder = null;

		TemplateExample.log(1,
		"Sending doShipProduct() a domestic order.");
		newOrder = new OrderType();
		newOrder.setDomestic(true);
		newOrder = ShippingRules.doShipProduct(newOrder);

		TemplateExample.log(1,
		"Sending doShipProduct() an international order.");
		newOrder = new OrderType();
		newOrder.setDomestic(false);
		newOrder = ShippingRules.doShipProduct(newOrder);

		TemplateExample.log(1,"End Template Design Pattern example.");
	} //endmain

	// ==============================================================
	public static void log(int aLevel, String aMsg) {
		boolean showLogTraceToSystemOut = true;
	  	if(showLogTraceToSystemOut) {
			String prefix = "";
		  	if(aLevel == 1) {
				prefix = "   ---> ";
		  	} else if(aLevel == 2) {
				prefix = "     ---> ";
		  	} else if(aLevel == 3) {
				prefix = "       ---> ";
		  	} //endif
		  	System.out.println(prefix + aMsg);
	  	} //endif
  	} //endmethod: log
} //endclass: TemplateExample.java


// ************************************************************************
interface ShippingRulesInterface {
	public boolean okToShipToCountry(OrderType order);
	public OrderType addInternationalShippingFee(OrderType order);
	public OrderType addSalesTax(OrderType order);
	public OrderType addShippingFee(OrderType order);
} //endclass: ShippingRulesInterface


// ************************************************************************
class OrderType {
	private boolean isDomestic = true;
	public String getCountryOrderGoesTo() {return( "MyCountryTisOfThee" );}
	public boolean isDomesticOrder() {return( isDomestic );}
	public void setDomestic(boolean aIsDomestic) {isDomestic = aIsDomestic;}
} //endclass: OrderType


// ************************************************************************
abstract class ShippingRules implements ShippingRulesInterface  {
	// ==============================================================
	public static OrderType doShipProduct(OrderType order) {
		ShippingRules rules = null;
		//-- Command Pattern to determine type of rule object to use
		if( order.isDomesticOrder() ) {
			rules = new ShippingRulesDomestic();
		} else {
			rules = new ShippingRulesInternational();
		}

		if( rules.okToShipToCountry(order) ) {
			order = rules.addInternationalShippingFee(order);
			order = rules.addSalesTax(order);
			order = rules.addShippingFee(order);
		} //endif
		return( order );
	} //endmethod doShipProduct

	// ==============================================================
	public boolean okToShipToCountry(OrderType order) {
		boolean isOkToShip = true;
		TemplateExample.log(2,
			"Implementing ShippingRules.okToShipToCountry().");
		/* ===================
		 * Since this is a generic process, body can be here in ancestor class.
		 * Conditional logic here to look at the country the order is going
		 * to and evaluating that country as to if it is acceptable to ship
		 * there or not and assigning the appropriate value of isOkToShip.
		 * For our purposes in this example, we will always return true, but
		 * if fully implemented this might look something like the following:
		 *
		 * String country = order.getCountryOrderGoesTo();
		 * if( CountrySecurityMgr.okToShipToCountry(country) ) {
		 * 		isOkToShip = true;
		 * } else {
		 * 		isOkToShip = false;
		 * }
		=================== */
		return( isOkToShip );
	} //endmethod okToShipToCountry

	// ==============================================================
	public OrderType addInternationalShippingFee(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addInternationalShippingFee " +
		"does nothing.");
		return( order );
	} //endmethod addInternationalShippingFee

	// ==============================================================
	public OrderType addSalesTax(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addSalesTax does nothing.");
		return( order );
	} //endmethod addSalesTax

	// ==============================================================
	public OrderType addShippingFee(OrderType order) {
		TemplateExample.log(3,
		"Stub ShippingRules.addShippingFee does nothing.");
		return( order );
	} //endmethod addShippingFee

} //endclass: ShippingRules


// ************************************************************************
class ShippingRulesDomestic extends ShippingRules  {
	// ==============================================================
	public OrderType addSalesTax(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesDomestic.addSalesTax().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addSalesTax

	// ==============================================================
	public OrderType addShippingFee(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesDomestic.addShippingFee().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addShippingFee
} //endclass: ShippingRulesDomestic


// ************************************************************************
class ShippingRulesInternational extends ShippingRules  {
	// ==============================================================
	public OrderType addInternationalShippingFee(OrderType order) {
		TemplateExample.log(2,
		"Implementing ShippingRulesInternational. " +
		"addInternationalShippingFee().");
		//< -- code to implement business logic of this method -- >
		return( order );
	} //endmethod addInternationalShippingFee
} //endclass: ShippingRulesInternational



Back to Thoughts On Technology Page | Back to Peter's Home Page | Back to Family Home Page