模板方法:在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤,他导致了一种反向的控制结构(这里指一个父类调用一个子类的操作,而不是相反)——好莱坞法则(别来调用我们(高层组件),我们会调用你(底层组件))
简单起见,我们用一个例子来探索一下模板方法,来自《Head Ffirst 设计模式》:存在两种饮料:Coffe和Tea
//我要煮咖啡、煮茶,这两种行为太相似了,我不想定义两个类让他们自己实现自己的,因为那样他们会有很多重复的代码
//所以我给他们提供一个模板
public abstract class CaffeineBeverage {
//这是模板,他们要严格遵照这里的步骤执行
final void prepareRecipe(){
//烧水
boilWater();
//煮
brew();
//倒进杯子里面
pourInCup();
//添加佐料,糖啊柠檬啊
addCondiments();
}
//茶和咖啡煮水没什么不一样吧?在父类可以统一定义他们的行为,同理,倒进杯子里面也一样
void boilWater(){System.out.println("Boiling water");}
void pourInCup(){System.out.println("Pouring into cup");}
//他们不一样的地方就在于怎么煮和加什么佐料,那就交给子类去实现吧
abstract void brew();
abstract void addCondiments();
}
接下来我要为子类茶、咖啡实现那些变化的部分
public class Coffee extends CaffeineBeverage {
@Override
void brew() {System.out.println("Dripping Coffee through filter");}
@Override
void addCondiments() {System.out.println("Adding Sugger and Milk");}
}
public class Tea extends CaffeineBeverage {
@Override
void brew() {System.out.println("Steeping the tea");}
@Override
void addCondiments() {System.out.println("Adding Lemon");}
}
到了这里,一个简单的模板方法就完成了,我们测试一下:
public class test {
public static void main(String[] args) {
System.out.println("我想喝茶");
Tea myTea = new Tea();
myTea.prepareRecipe();
System.out.println("我想喝咖啡");
Coffee myCoffee = new Coffee();
myCoffee.prepareRecipe();
}
}
//我想喝茶
//Boiling water
//Steeping the tea
//Pouring into cup
//Adding Lemon
//我想喝咖啡
//Boiling water
//Dripping Coffee through filter
//Pouring into cup
//Adding Sugger and Milk
可以看到,变化和不变的部分正在协调工作,而且少了很多重复的代码——这就是模板方法的威力,好莱坞法则在这里的体现是:CaffeineBeverage能狗控制算法,只有在需要子类实现某个方法时才调用子类,而子类是仅仅提供一些细节,子类实现的方法如果没有被调用,绝对不会直接调用抽象类。但是,模板方法还可以更进一步去运用,我们引入钩子这个概念:钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩,并且由子类决定是否要挂钩,直接修改代码:
public abstract class CaffeineBeverageWithHook {
void prepareRecipe(){
boilWater();
brew();
pourInCup();
//默认是添加的
if(customerWantsCondiments()){
addCondiments();
}
}
//他们不一样的地方就在于怎么煮和加什么佐料,那就交给子类去实现吧
abstract void brew();
abstract void addCondiments();
//茶和咖啡煮水没什么不一样吧?在父类可以统一定义他们的行为,同理,倒进杯子里面也一样
void boilWater(){System.out.println("Boiling water");}
void pourInCup(){System.out.println("Pouring into cup");}
//让子类决定是否需要添加佐料
boolean customerWantsCondiments(){
return true;
}
}
那么子类我们们也只是加一个判断是否执行添加佐料的方法,这里直接在控制台询问你(填写y/n)
只列出一个代码,大同小异
public class CoffeeWithHook extends CaffeineBeverageWithHook {
@Override
void brew() {System.out.println("Dripping Coffee through filter");}
@Override
void addCondiments() {System.out.println("Adding Suger and Milk");}
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("Would you like milk and suger with your coffee (y/n)?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if(answer==null){
return "no";
}
return answer;
}
}
测试方法:
public class BeverageTestDrive {
public static void main(String[] args) {
TeaWithHook teaHook = new TeaWithHook();
CoffeeWithHook coffeeHook = new CoffeeWithHook();
System.out.println("\nMaking tea...");
teaHook.prepareRecipe();
System.out.println("\nMaking coffee");
coffeeHook.prepareRecipe();
}
}
Making tea...Boiling water
Dripping Tea through filter
Pouring into cup
Would you like milk and suger with your coffee (y/n)?
y//这时候在控制台输入y
Adding Lemon
Making coffee
Boiling water
Dripping Coffee through filter
Pouring into cup
Would you like milk and suger with your coffee (y/n)?
y//同上
Adding Suger and Milk
其实,在我们平常编写java代码的时候,经常会碰到的compareTo是一个模板方法的应用,他要求我们实现一个comparable接口,然后实现compareTo方法,比如:
《Head First设计模式》 这里比较鸭子的重量,我们可以看到它实现了这个接口(这个接口就等于模板方法里面让子类实现的部分)
public class Duck implements Comparable{
String name;
int weight;
public Duck(String name, int weight) {
super();
this.name = name;
this.weight = weight;
}
@Override
public String toString() {
return "Duck [name=" + 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;
}
}
}
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("After sorting:");
display(ducks);
}
public static void display(Duck[] ducks){
for(int i=0;i<ducks.length;i++){
System.out.println(ducks[i]);
}
}
}
结果就不贴出来了,可想而知是排序好的数组在书本里面介绍源码里面的方法是这样子的(查看过源码,很明显源码还考虑过其他的排序方式,归并排序等,这里只是一个简洁版,执行的方式确实如此)
// public static void sort(Object[] a){
// Object[] aux = a.clone();
// mergeSort(aux, a, 0, a.length, 0);
// }
//
// public static void mergeSort(Object[] src,Object[] dest,
// int low,int high,int off){
// for(int i=low;i<high;i++){
// for(int j=i;j>low &&
// ((Comparable)dest[j-1]).compareTo((Comparable)dest[j])>0;j--){//这里用到了子类实现的部分
// swap(dest,j,j-1);
// }
// }
// return;
// }
适用性:
1、一次实现一个算法不变的部分,并将可变的行为交给子类来实现
2、各子类中公共行为应被提取出来并且集中到一个公共父类中避免重复代码。首先识别现有代码的不同之处,并将不同之处分离为新的操作。最后,用一个调用这些新的操作的模板方法来替换这些不同的代码
3、控制子类扩展。比如钩子方法
似乎漏了类图,不过很简单:
模板方法相对来说还是比较简单的,以后有要补充的会在这里继续补充,关于模板方法和策略模式的区别,将会在策略模式的笔记中小比较一下