一.前言
what is templatemethod,一句话我的理解:在超类并定义了算法的步骤,定义了共有步骤的具体实现,并允许子类为一个或者多个步骤提供实现。
解释:比如在超类中定义算法步骤包括5步,其中1、3、5是在超类中实现的,2、4的实现在子类中有不同,所以子类各自实现。
二.简单的例子
泡茶和冲咖啡,他们的步骤分别是:泡茶:
1.把水煮沸
2.用沸水浸泡茶叶
3.把茶水倒进杯子
4.加柠檬
冲咖啡:
1.把水煮沸
2.用沸水冲泡咖啡
3.把咖啡倒进杯子
4.加糖和牛奶
对应的代码如下:
public class Tea {
void prepareRecipe() {
boilWater();
steepTeaBag();
pourInCup();
addLemon();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void steepTeaBag() {
System.out.println("Steeping the tea");
}
public void addLemon() {
System.out.println("Adding Lemon");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
}
public class Coffee {
void prepareRecipe() {
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
public void boilWater() {
System.out.println("Boiling water");
}
public void brewCoffeeGrinds() {
System.out.println("Dripping Coffee through filter");
}
public void pourInCup() {
System.out.println("Pouring into cup");
}
public void addSugarAndMilk() {
System.out.println("Adding Sugar and Milk");
}
}
很明显,这两个类是存在了重复代码的,既然他们步骤是类似的,应该将共同的部分抽取出来,放入一个基类中。我们不难得出下面的关系:
饮料超类:
public abstract class Beverage {
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 Tea extends Beverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
咖啡:
public class Coffee extends Beverage {
public void brew() {
System.out.println("Dripping Coffee through filter");
}
public void addCondiments() {
System.out.println("Adding Sugar and Milk");
}
}
想到有个问题,像我们家妹纸,喝东西都喜欢原味的,我这强制加了Condiment好像不好诶,肿么办,为了我们家妹纸,当然不能提供这么愚蠢的设计,有办法的,我们使用钩子,改造上述代码改造如下:
public abstract class BeverageWithHook {
void prepareRecipe() {
boilWater();
brew();
pourInCup();
//在这里控制是否执行addCondiments
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 TeaWithHook extends BeverageWithHook {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
public boolean customerWantsCondiments() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}
//在代码中添加供用户输入的部分,模拟用户的选择
private String getUserInput() {
// get the user's response
String answer = null;
System.out.print("Would you like lemon with your tea (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;
}
}
//类似的,拥有钩子的咖啡
public class CoffeeWithHook extends BeverageWithHook {
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的数组类提供了一个模版的排序方法sort,源码如下:
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){
for(int i=low;i<high;int off){
for(int j=i;j>low&&((Comparable) dest[j-1]).compareTo((Comparable) dest[j])>0;j--){
swap(dest,j,j-1);
}
}
}
从上面都代码可以看出,会调用数组中元素的compareTo方法,这个方法就需要在元素子类中实现这个方法,例如排序一个鸭子类:
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;
}
}
}
测试函数如下:
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]);
}
}
}
这个跟模版方法虽然不能完全重合,但是基本思想是符合的:
1.鸭子数组
2.调用Array.sort
3.实现数组中鸭子的compareTo
自动脑补一下,以上。