定义
在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。
通俗理解就是:在一个基类里面,定义了一个算法的流程(里面调用了很多方法),其中一些方法是基类已经实现的,一些是需要具体子类或实现类来实现的,还有一下是钩子(允许子类或实现类重写,如果不重写就按默认的执行),那么那个基类的流程方法就是模板方法。即算法框架的轨迹已经定义好了,部分实现允许按实际情况自己定义。模块方法模式在框架的架构中大量使用。
生活案例
在一个饮料冲泡的流程中,对于不同的饮料,很多方法都是相同的,只有部分实现是根据不同的饮料来实现的。所以可以定义一个基类:(prepareRecipe()为模板方法)
其中boilWater()和pourInCup()是每种饮料都需要的,所以基类来实现这两种方法,brew()和addCondiments()是根据不同饮料来实现的,所以定义为抽象类,需要子类来实现。
public abstract class CaffeineBeverage {
final void prepareRecipe() {
boilWater();
brew();
pourInCup();
addCondiments();
}
abstract void brew();
abstract void addCondiments();
void boilWater() {
System.out.println("Boiling water");
}
void pourInCup() {
System.out.println("Pouring into cup");
}
}
咖啡类(茶类类似,就不贴代码了):
public class Coffee extends CaffeineBeverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
钩子的定义:钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
所以在饮料冲泡的流程中,对于要不要加入调料(即addCondiments)应该可以选择。
所以修改后的基类是:
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;
}
}
咖啡类:
public class CoffeeWithHook extends CaffeineBeverageWithHook {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar 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.print("Would you like milk and sugar with your coffee (y/n)? ");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException ioe) {
System.err.println("IO error trying to read your answer");
}
if (answer == null) {
return "no";
}
return answer;
}
}
这样就可以让顾客自行选择要不要调料了,最重要的是这样使得了框架更灵活了,可以选择是否执行某个步骤,而不是按照一个死的流程来走。
java API中使用到的模板方法模式
这个是Arrays的sort()方法(就是排序功能),注意,这个例子里面没有用到像上面那个例子的抽象父类,而是用了一个静态方法(在这里也就是模板方法),这个静态方法有调用了本类的方法(即基类已经实现好的方法),也有调用需要实现类(这里不是子类了,而是接口的实现类)实现的方法。所以整个sort()流程的框架是定义好的,需要的是根据不同的实现类来实现部分具体的方法。
部分Arrays的源码:
public static void sort(Object[] a) {
Object[] aux = (Object[])a.clone();
mergeSort(aux, a, 0, a.length, 0);
}
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
其中compareTo()方法是需要具体实现类来实现的,其他是基类Arrays已经实现的(有些常量和方法没贴出来,读者可以自行看java源码)。
那么具体实现类是怎样的:
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;
}
}
}
具体实现类需要实现Comparable接口,因为在基类算法流程里面需要将这个类强转为Comparable类型;具体实现类也需要实现compareTo()这个方法,流程里面也需要调用这个方法(即部分需要根据具体实现类来实现的方法)。
这样鸭子的排序流程就可以顺利进行了。
模板方法模式整体就是这样,大家如果有什么想法想交流交流,欢迎在下面评论哦。
