以《HeadFirst设计模式》中的咖啡和茶的冲泡过程的实现为例讲述模板方法模式的应用场景。
一、问题描述
星巴兹咖师傅的训练手册:
咖啡冲泡法:
(1)把水煮沸 (2)用沸水冲泡咖啡 (3)把咖啡倒进杯子 (4)加糖和牛奶
茶冲泡法:
(1)把水煮沸 (2)用水浸泡茶叶 (3)把茶倒进杯子 (4)加柠檬
二、逐步抽象
1.对于以上咖啡和茶的冲泡流程,我们一般会分别将四个步骤编写为四个方法,以冲泡咖啡为例,代码为:
public class Coffee
{
//咖啡的冲泡流程(即模板方法)
void prepareRecipe()
{
boilWater();
brewCoffeeGrinds();
pourInCup();
addSugarAndMilk();
}
// (1)把水煮沸
public void boilWater(){...}
// (2)用沸水冲泡咖啡
public void brewCoffeeGrinds(){...}
// (3)把咖啡倒进杯子
public void pourInCup(){...}
// (4)加糖和牛奶
public void addSugarAndMilk(){...}
}
(对于Tea类也是如此)
2.可以发现,茶和咖啡的冲泡流程中的有的步骤是相同的,例如:茶和咖啡的冲泡流程中的(1)和(3)是相同的。同样,对于步骤(2)和步骤(4)也是相似的。所以,我们的处理是:
从Coffee和Tea类抽象出两者的超类,以抽象类CaffeineBeverage(咖啡因饮料)命名,将两者公有的方法步骤即(1)和(3)放在超类中实现,流程即prepareRecipe()方法为模板方法,在超类中将步骤(2)和(4)定义为抽象方法,以便子类Tea和Coffee各自实现步骤(2)和步骤(4)。
三、类图
四、代码实现(不带钩子)
抽象超类CaffeineBeverage:
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");
}
}
茶Tea实现:
public class Tea extends CaffeineBeverage {
public void brew() {
System.out.println("Steeping the tea");
}
public void addCondiments() {
System.out.println("Adding Lemon");
}
}
咖啡Coffee实现:
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");
}
}
测试:
Tea tea = new Tea();
Coffee coffee = new Coffee();
System.out.println("Making tea...");
tea.prepareRecipe();
System.out.println("Making coffee...");
coffee.prepareRecipe();
五、进一步设计:在冲泡方法(模板方法)中加入钩子
有时候顾客可能不需要向茶或咖啡中加入调料,这时我们就必须在冲泡咖啡或茶之前问一下顾客。
类的结构不变,只是抽象类中模板方法prepareRecipe的实现过程稍微修改一下,以便让子类可以决定是否执行步骤(4),prepareRecipe方法如下:
void prepareRecipe() {
boilWater();
brew();
pourInCup();
//让子类可以决定算法是否执行该步骤
//customerWantsCondiments()方法是一个钩子,父类对其其默认实现返回true,即默认执行步骤(4)
if (customerWantsCondiments()) {
addCondiments();
}
}
子类可以选择是否覆盖父类中的customerWantsCondiments()方法,此处钩子作为条件控制影响了抽象类中的算法流程!
六、带钩子的代码实现
抽象父类:
public abstract class CaffeineBeverageWithHook {
void prepareRecipe() {
boilWater();
brew();
pourInCup();
//让子类可以决定算法是否执行该步骤
//customerWantsCondiments()方法是一个钩子,父类对其默认实现返回true,即默认执行步骤(4)
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;
}
}
茶类:
public class TeaWithHook extends CaffeineBeverageWithHook {
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() {
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;
}
}
测试:
TeaWithHook teaHook = new TeaWithHook();
CoffeeWithHook coffeeHook = new CoffeeWithHook();
System.out.println("\nMaking tea...");
teaHook.prepareRecipe();
System.out.println("\nMaking coffee...");
coffeeHook.prepareRecipe();
总结:
(1)抽取公共的算法部分将其定义为抽象类中的具体方法。
(2)对于子类中不同的算法实现的部分定义为抽象类中的抽象方法让子类实现。
(3)使用钩子,给子类一定的决定权(钩子的用处很多)。
(4)逐步抽象。
转载请注明出处:http://blog.csdn.net/jialinqiang/article/details/8869352