什么是模板方法模式
在一个方法中定义一个算法骨架,而将一些步骤延迟到子类中,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
一个小例子
我们要泡茶和冲咖啡,而泡茶和冲咖啡有些相同的地方,也有些不同的地方,比如烧水和把饮料(咖啡或者茶)装进杯子这些步骤都是一样的,不同的是咖啡可能和茶制成后添加的调味剂不一样
这样的话我们可以写一个基类把所有相同的步骤抽取出来,而不同的步骤延伸到子类里去进行
首先是含有咖啡因饮料的基类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| public abstract class CaffeineBeverage { final void prepareRecipe(){ boilWater(); brew(); pourInCup(); if(customerWantsCondiments()){ addCondiments(); } } abstract void brew(); abstract void addCondiments(); void boilWater(){ System.out.println("把水煮沸"); } void pourInCup(){ System.out.println("倒进杯子"); } boolean customerWantsCondiments(){ return true; } }
|
咖啡
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Coffee extends CaffeineBeverage { @Override void brew() { System.out.println("冲泡咖啡"); } @Override void addCondiments() { System.out.println("加点牛奶和糖"); } }
|
茶
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public class Tea extends CaffeineBeverage { @Override void brew() { System.out.println("冲泡茶叶"); } @Override void addCondiments() { System.out.println("加点柠檬"); } }
|
测试类
1 2 3 4 5 6 7 8 9
| public class Test { public static void main(String[] args){ CaffeineBeverage tea = new Tea(); tea.prepareRecipe(); CaffeineBeverage coffee = new Coffee(); coffee.prepareRecipe(); } }
|
输出结果
把水煮沸
冲泡茶叶
倒进杯子
加点柠檬
把水煮沸
冲泡咖啡
倒进杯子
加点牛奶和糖
我们把相同的步骤都放到基类里去执行了,而子类只处理关于自己的步骤就好了,这就是一种简单的模板方法的实现
但是有的客户可能有些特殊的要求,比如有的不喜欢在咖啡或者茶里添加一些调料,他们喜欢喝原汁原味的,这个时候钩子就产生了用途
什么是钩子?
钩子是一种被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,要不要挂钩由子类自己决定
上边的咖啡因基类CaffeineBeverage已经实现了默认的钩子,现在我们让子类来决定要不要使用钩子
首先是咖啡类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| public class CoffeeWithHook extends CaffeineBeverage { @Override void brew() { System.out.println("coffee"); } @Override void addCondiments() { System.out.println("加糖和牛奶"); } public boolean customerWantsCondiments(){ String answer = getUserInput(); if(answer.toLowerCase().startsWith("y")){ return true; }else{ return false; } } private String getUserInput(){ String answer = null; System.out.println("是否想要加点牛奶和咖啡呢(y/n)?"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); try { answer = bufferedReader.readLine(); } catch (IOException e) { System.out.println("IOException"); } if(answer == null){ return "no"; } return answer; } }
|
茶类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| public class TeaWithHook extends CaffeineBeverage { @Override void brew() { System.out.println("Tea"); } @Override void addCondiments() { System.out.println("加点柠檬"); } public boolean customerWantsCondiments(){ String answer = getUserInput(); if(answer.toLowerCase().startsWith("y")){ return true; }else{ return false; } } private String getUserInput(){ String answer = null; System.out.println("是否想要加点柠檬呢(y/n)?"); BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in)); try { answer = bufferedReader.readLine(); } catch (IOException e) { System.out.println("IOException"); } if(answer == null){ return "no"; } return answer; } }
|
测试类中的代码如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public class Test { public static void main(String[] args){ CaffeineBeverage coffeeWithHook = new CoffeeWithHook(); CaffeineBeverage teaWithHook = new TeaWithHook(); System.out.println("\nmaking tea"); teaWithHook.prepareRecipe(); System.out.println("\nmaking coffee"); coffeeWithHook.prepareRecipe(); } }
|
输出结果
making tea
把水煮沸
Tea
倒进杯子
是否想要加点柠檬呢(y/n)?
y
加点柠檬
making coffee
把水煮沸
coffee
倒进杯子
是否想要加点牛奶和咖啡呢(y/n)?
n
加了钩子之后我们看到子类可以决定到底是否要调用addCondiments方法来为饮料加点料
一个鸭子排序的模板方法
我们有一个鸭子类实现了Comparable接口,并且重写了compareTo方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| 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+" weight"+weight; } @Override public int compareTo(Object o) { Duck otherDuck = (Duck) o; if(this.weight < otherDuck.weight){ return -1; }else if(this.weight == otherDuck.weight){ return 0; }else{ return 1; } } }
|
接下来我们要对一堆鸭子根据体重排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| public class Test { 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("\n开始排序鸭子之前"); display(ducks); Arrays.sort(ducks); System.out.println("\n排序鸭子后"); display(ducks); } public static void display(Duck[] ducks){ for(int i = 0,size = ducks.length; i < size; i++){ System.out.println(ducks[i]); } } }
|
我们使用了Arrays.sort方法实现,输出结果如下
开始排序鸭子之前
daffy weight8
Dewey weight2
Howard weight7
Louie weight2
Donald weight10
Huey weight2
排序鸭子后
Dewey weight2
Louie weight2
Huey weight2
Howard weight7
daffy weight8
Donald weight10
我们观察鸭子排序内部的工作
1.首先我们需要一个鸭子数组:
Duckp[] ducks = {new Duck(“daffy”,8),…..}
2.然后调用Arrays类的sort()方法,并传入鸭子数组:
Arrays.sort(ducks);
for(int i = low; i < high; i++){
…compareTo()…
…swap()…
}
sort()方法控制算法,没有类可以改变这一点。sort()依赖一个Comparable类提供的compareTo()的实现。
3.想要排序一个数组,需要一次次的比较两个项目,直到整个数组都排序完毕
ducks[0].compareTo(ducks[1]);
4.如果鸭子的次序不对,就用Arrays的swap()方法将两者对调
5.排序方法会持续的比较并对调鸭子,直到整个数组的次序是正确的。
总结
1.”模板方法”定义了算法的步骤,把这些步骤的实现延迟到子类
2.模板方法为我们提供了一种代码复用的技巧
3.模板方法的抽象类可以定义具体方法,抽象方法和钩子
4.抽象方法由子类实现
5.钩子是一种方法,它在抽象类中不做事,或者只做默认的事,子类可以选择要不要去覆盖它
6.为了防止子类改变模板方法中的算法,可以将模板方法声明为final
7.“好莱坞”原则告诉我们,将决策权放在放在高层模块中,以便决定如何以及何时调用底层模块
8.真实的模板方法使用犹如上边的鸭子排序一样,很难一眼被我们认识
9.策略模式和模板方法模式都封装算法,一个用组合,一个用继承
10.工厂方法是模板方法的一种特殊版本