1.这个模式比较简单,我们举一个银行贷款申请流程程序的例子(这个并非是书中的例子):
申请贷款,银行要检查这个客户的一些事宜,譬如客户收支状况记录、从三个地方拿到他的信用记录、得到其他已有相关债务信息、得到借债人股票市值、得到借债人未来收入预期分析等等。我们可以设计如下一个模板方法:
abstract class CheckBackground { public abstract void checkBank(); public abstract void checkCredit(); public abstract void checkLoan(); public abstract void checkStock(); public abstract void checkIncome(); public void check() {//模板方法为非抽象的 checkBank(); checkCredit(); checkLoan(); checkStock(); checkIncome(); } }
我们的贷款类就可以如下这么设计了,实现了模板方法中的抽象方法:
class LoanApp extends CheckBackground { private String name; public LoanApp(String name) { this.name = name; } public String getName() { return name; } public void checkBank() { //ck acct, balance System.out.println("check bank..."); } public void checkCredit() { //ck score from 3 companies System.out.println("check credit..."); } public void checkLoan() { //ck other loan info System.out.println("check other loan..."); } public void checkStock() { //ck how many stock values System.out.println("check stock values..."); } public void checkIncome() { //ck how much a family make System.out.println("check family income..."); } //other methods }
2.这个模式的核心思想是“抽象”。首先作为模板方法,它必须是一个方法(废话……),它可以作为一个算法的例子,在模板方法中,每一个步骤都要由另一个方法来完成,某些方法是由这个类完成,某些则是由这个类的子类完成(需要由子类提供的方法必须在超类中声明为抽象)——模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现,实质上模板方法提供了一个算法的框架(Framework),步骤定死,而实现又子类进行。这个模式的优势在于最大限度的发挥抽象能力,保证程序需求变更或者优化升级时必须进行的变更最小。注意、包含模板方法的类本身要是抽象的。
3.我们能不能比较智能的控制算法呢,比如某些时候这个客户是一个VIP客户,我们不需要对其Income去进行检查,此时可以使用Hook(挂钩),在需要的时候把一些方法“挂上”。我们这样设计这个模板方法类:
abstract class CheckBackground { public abstract void checkBank(); public abstract void checkCredit(); public abstract void checkLoan(); public abstract void checkStock(); public abstract void checkIncome(); public void check() {//模板方法为非抽象的 checkBank(); checkCredit(); checkLoan(); if(!ischeckVIP){//使用钩子,子类可以根据需要进行覆盖 checkStock(); } checkIncome(); } public boolean ischeckVIP(){//钩子函数 return false; } }
挂钩函数除了可以让子类控制一些算法的流程以外,另一个功能是让子类有机会对模板方法中某些即将发生的步骤做出反应。
注意,由于子类必须实现抽象类中的所有抽象方法,所以应该保持抽象方法的数目越少越好,这就需要你在设计的时候对算法内的数据不要切割太细,同时粒度太大程序的弹性又受到限制,这就需要你去权衡了,程序设计中太多的地方是平衡和妥协了。
4.我们此时引出一个新的OO原则——好莱坞原则:别电话给我们,我们会打电话给你——别调用我们,我们会去调用你。
在这个原则之下,我们允许低层组件将自己挂钩到系统上,而高层组件会决定什么时候和如何使用这些低层组件,我们把决策权放到高层模块中。也就是说,高层组件对待低层组件的方式就是上边那句黑体字的原则。书中举了一个咖啡与茶的例子(有趣的是,《咖啡与茶》是丁薇早年间的一张唱片的名字,我高一的时候买到了正版卡带)来解释模板方法模式与这个原则关系,见原书(英文版)P297。这个原则和依赖倒置原则的关系是,后者交给我们如何尽量避免使用具体类,而多使用抽象,注重体现如何避免依赖。前者则是通过创建框架和组件的一种技巧,注重创建一个有弹性的设计,同时又防止其他类太过依赖它们,避免类的环状依赖。值得注意的是,工厂方法是模板方法的一种特殊版本。
5.接着,我们看看Java中有什么是使用了这个模式的:数组排序方法sort( )
sort()的设计者希望这个方法能适用于所有的数组,所以将其设置为静态方法。而同时你必须实现compartTo方法后才能使用sort方法,为了达到这一点,设计者利用了Comparable接口,提供这个接口所声明的方法,也就是compartTo。
我们现在利用这个接口去设计一个鸭子的类,试图根据体重去对鸭子进行比较。
public class Duck implements Comparable { String name; int weight; public Duck(String name, int weight) { this.name = name; this.weight = weight; } public String toString() { return name + " weighs " + weight; } public int compareTo(Object object) { Duck otherDuck = (Duck)object; if (this.weight < otherDuck.weight) { return -1; } else if (this.weight == otherDuck.weight) { return 0; } else { // this.weight > otherDuck.weight return 1; } } }
这样,我们就能直接使用数组中的sort方法了:
import java.util.ArrayList; import java.util.Arrays; public class DuckSortTestDrive { public static void main(String[] args) { Duck[] ducks = { new Duck("Daffy", 8), new Duck("Dewey", 2), new Duck("Howard", 7), new Duck("Louie", 2), new Duck("Donald", 10), new Duck("Huey", 2) }; System.out.println("Before sorting:"); display(ducks); Arrays.sort(ducks); System.out.println("/nAfter sorting:"); display(ducks); } public static void display(Duck[] ducks) { for (int i = 0; i < ducks.length; i++) { System.out.println(ducks[i]); } } }
这和模板方法又有什么关系呢?
完成Comparable接口的compareTo方法使得元素自行提供比较大小的算法部分。这就有点像前边我们的贷款程序中自己定义如何检查客户收入的函数一样。
我们再举两个例子:Swing:
import java.awt.*; import javax.swing.*; public class MyFrame extends JFrame { public MyFrame(String title) { super(title); this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); this.setSize(300,300); this.setVisible(true); } public void paint(Graphics graphics) {//默认情况下,它什么都不做,是一个挂钩,通过覆盖该方法,你可以将自己的代码插入JFrame中, super.paint(graphics); String msg = "I rule!!"; graphics.drawString(msg, 100, 100); } public static void main(String[] args) { MyFrame myFrame = new MyFrame("Head First Design Patterns"); } }
Applet:具体的applet大量使用挂钩函数提供行为。
import java.applet.Applet; import java.awt.Graphics; public class MyApplet extends Applet { String message; public void init() { message = "Hello World, I'm alive!"; repaint(); } public void start() { message = "Now I'm starting up..."; repaint(); } public void stop() { message = "Oh, now I'm being stopped..."; repaint(); } public void destroy() { message = "Goodbye, cruel world"; repaint(); } public void paint(Graphics g) { g.drawString(message, 5, 15); } }
6.最后,我们比较一下模板方法模式和策略模式的异同。
前者定义一个算法的大纲,由子类(注意这里是继承)定义其中的某些步骤的内容,这样算法的细节可有不同,但是算法的结构和流程保持不变,它对算法有更多的控制权。而后者定义了一个算法的家族,并让这些算法互换,正因为每一个算法都被封装了起来,所以客户可以轻易地使用不同的算法(不是通过继承而是接口的组合),它则更有弹性。