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