模版方法模式
目录
情景
泡茶和冲咖啡,代码如下:
public class Coffee {
public void prepareRecipe(){
boilWater();
makeCoffee();
pourInCup();
addSugarAndMilk();
}
public void boilWater(){
System.out.println("正在烧水");
}
public void addSugarAndMilk(){
System.out.println("正在加糖和牛奶");
}
public void pourInCup(){
System.out.println("正在将咖啡倒进杯子");
}
public void makeCoffee(){
System.out.println("正在用沸水煮咖啡");
}
}
public class Tea {
public void prepareRecipe(){
boilWater();
makeTea();
pourInCup();
addLemon();
}
public void boilWater(){
System.out.println("正在烧水");
}
public void addLemon(){
System.out.println("正在加柠檬");
}
public void pourInCup(){
System.out.println("正在将茶倒进杯子");
}
public void makeTea(){
System.out.println("正在用沸水泡茶");
}
}
模板方法
上述重复代码,如何复用?重新设计如下:
/**
* final 方法不希望子类去覆盖
* 抽象方法延迟到子类中实现
*/
public abstract class CaffeineBeverage {
/*
* 模板方法
*/
final public void prepareRecipe(){
boilWater();
makeIt();
pourInCup();
addCondiments();
}
final public void boilWater(){
System.out.println("正在烧水");
}
final public void pourInCup(){
System.out.println("正在将咖啡倒进杯子");
}
public abstract void addCondiments();
public abstract void makeIt();
}
public class Coffee extends CaffeineBeverage{
@Override
public void addCondiments() {
System.out.println("正在加糖和牛奶");
}
@Override
public void makeIt() {
System.out.println("正在用沸水煮咖啡");
}
}
public class Tea extends CaffeineBeverage{
@Override
public void addCondiments() {
System.out.println("正在加柠檬");
}
@Override
public void makeIt() {
System.out.println("正在用沸水泡茶");
}
}
public class Test {
public static void main(String[] args) {
CaffeineBeverage coffee = new Coffee();
coffee.prepareRecipe();
System.out.println("-----");
CaffeineBeverage tea = new Tea();
tea.prepareRecipe();
}
}
使用钩子
现在增加一个需求,喝咖啡的顾客可以自主选择是否需要调料。这时就用到了钩子。
代码做一下修改:
public abstract class CaffeineBeverage {
final public void prepareRecipe(){
boilWater();
makeIt();
pourInCup();
if(customWantsCondiments()){
addCondiments();
}
}
final public void boilWater(){
System.out.println("正在烧水");
}
final public void pourInCup(){
System.out.println("正在将咖啡倒进杯子");
}
public abstract void addCondiments();
public abstract void makeIt();
public boolean customWantsCondiments(){
// 默认认为顾客是想要调料的
return true;
}
}
public class CoffeeWithHook extends CaffeineBeverage{
@Override
public boolean customWantsCondiments() {
return getUserInput();
}
@Override
public void addCondiments() {
System.out.println("正在加糖和牛奶");
}
@Override
public void makeIt() {
System.out.println("正在用沸水煮咖啡");
}
private boolean getUserInput(){
String answer = null;
System.out.println("Would you like sugar and milk with your coffee (y/n) ?");
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try{
answer = in.readLine();
}catch(Exception e){
System.out.println("IO error to read your answer");
}
if(answer.startsWith("y")){
return true;
}else{
return false;
}
}
}
public class Tea extends CaffeineBeverage{
@Override
public void addCondiments() {
System.out.println("正在加柠檬");
}
@Override
public void makeIt() {
System.out.println("正在用沸水泡茶");
}
}
总结
【定义】已经定义了算法骨架,而将一些步骤延迟到子类中。
【本质】固定算法结构。
【优点】
- 代码复用性高。
- 容易扩展。
- 基类专注算法架构,子类专注实现,架构清晰。
- 比较灵活,子类可以使用钩子方法对父类算法进行反向约束。
【缺点】
模板类和子类耦合,变更算法骨架需谨慎。
模版方法的应用
- Spring 中有太多地方使用了。
工作需要定时从一些网站上抓取数据存储到数据库中,因此创建了一个抽象类 Job,类中定义算法骨架:
- 抽取数据。
- 分析数据。
- 存储有用信息。
- 包括处理异常,如果抓取失败,按照既定策略进行重试。
前三个方法是抽象方法,每个子类的实现逻辑千差万别。