目录
1、引入
喝茶与泡咖啡过程:
准备喝咖啡时,我们的步骤是
1.把水煮沸
2.用沸水冲泡咖啡
3.倒进杯子
4.加糖和牛奶
准备喝茶的时,步骤是
1.把水煮沸
2.用沸水浸泡茶叶
3.倒进杯子
4.加柠檬
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 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...");
}
}
可以发现,其实泡茶和泡咖啡的过程就是一个固定骨架步骤的“算法”,我们可以抽象为:
- 煮沸水
- 冲泡
- 将泡好的饮料倒入杯子
- 根据需求加入调料
标蓝部分为算法中不一样的部分,如何解决?下面,我们用“模板方法模式”来解决这种不一致。
2、使用模板模式
模板方式:定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现,模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
//模板
public abstract class HotDrink {
//步骤是固定的,所以使用final修饰过程
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
void boilWater() {
System.out.println("boiing water...");
}
//不一样的部分做成抽象类,等待子类去实现
abstract void brew();
abstract void addCondiments();
void pourInCup(){
System.out.println("pouring into cup...");
}
}
此时后面的coffee和tea,只需实例化各自特殊的部分,整个烘焙的过程,还是使用模板进行操作(并且已经使用final写死)
//继承模板,然后将操作进行实例化
public class Coffee extends HotDrink{
@Override
void brew() {
System.out.println("driping coffee through filter...");
}
@Override
void addCondiments() {
System.out.println("adding sugar and milk");
}
}
public class Tea extends HotDrink {
void brew() {
System.out.println("Steeping the tea");
}
void addCondiments() {
System.out.println("Adding Lemon");
}
}
模板方法将算法定义成一组步骤,其中的任何步骤都可以是抽象的,由子类负责实现。这样可以确保算法的结构保持不变,同时由子类提供部分的实现。
所以,模板方法就是定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。
重点描述模板模式的类图
1、templateMethod:
模板方法,就是此处的烘焙过程。
2、AbsOperation:
抽象方法,需要子类自己去实现特殊功能。(例如此处的加调料)
3、concreteOp:
具体方法,由模板进行操作,统一的行为。(例如此处的少水)
4、hook
可选的操作,由模板超类进行实现,允许子类进行重写实现特殊功能。(就是有些特殊功能,超类已经实现,子类自行决定是否重写)
对hook的理解
hook允许子类重载步骤,可以确保不同需求步骤的算法的实现。
钩子是一种被声明在抽象类中的方法,但只有空的或者默认的实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。
简单点说,钩子就是对算法中步骤的不同(省略或增加)而设计的。在上面的代码中,我们写了一个钩子来决定是否加调料
/**
* “钩子”方法。顾客决定是否加调料
*/
public Boolean customerWantsCondiment(){
return true;
}
/**
* 模板方法,准备饮料
*/
public final void prepareRecipe(){
boilWater();
brew();
//用于模板方法的算法中可选部分的控制
if(customerWantsCondiment())
addCondiment();
pourInCup();
}
public class Tea extends CaffeineBeverage{
@Override
public void brew() {
System.out.println("浸泡茶叶");
}
@Override
public void addCondiment() {
System.out.println("添加蜂蜜");
}
//覆盖父类的“钩子”方法,更改算法中的可选部分
@Override
public Boolean customerWantsCondiment(){
//询问顾客是否需要调料
String answer = askCustomerNeedCondiment();
if("y".equals(answer))
return true;
else
return false;
}
private String askCustomerNeedCondiment() {
String answer = null;
System.out.println("请问您要不要加蜂蜜?请回答y或n");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
answer = in.readLine();
} catch (IOException e) {
e.printStackTrace();
}
return answer;
}
}