0%

七、模板方法模式

示例

以冲泡咖啡喝茶为例:
咖啡冲泡法:

  1. 把水煮沸
  2. 用沸水冲泡咖啡
  3. 把咖啡倒进杯子
  4. 加糖加牛奶

茶冲泡法:

  1. 把水煮沸
  2. 用沸水浸泡茶叶
  3. 把茶倒进杯子
  4. 加柠檬

茶和咖啡的冲泡方法非常相似。

Coffee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Coffee {
void prepareRecipe(){
// 把水煮沸
boilWater();
// 用沸水冲泡咖啡
brewCoffeeGrinds();
// 把咖啡倒进杯子
pourInCup();
// 加糖加牛奶
addSugarAndMilk();
}
private void addSugarAndMilk() {
System.out.println("加糖加牛奶");
}
private void pourInCup() {
System.out.println("把咖啡倒进杯子");
}
private void brewCoffeeGrinds() {
System.out.println("用沸水冲泡咖啡");
}
private void boilWater() {
System.out.println("把水煮沸");
}
}

Tea.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Tea {
void prepareRecipe(){
// 把水煮沸
boilWater();
// 浸泡茶叶
stepTeaBag();
// 把茶倒进杯子
pourInCup();
// 加柠檬
addLemon();
}
private void addLemon() {
System.out.println("加柠檬");
}
private void pourInCup() {
System.out.println("把茶倒进杯子");
}
private void stepTeaBag() {
System.out.println("浸泡茶叶");
}
private void boilWater() {
System.out.println("把水煮沸");
}
}

抽象

可以看到上面的Coffee和Tea中有重复代码,都含有prepareRecipe()、boilWater()和pourInCup()方法,并且boilWater()和pourInCup()实现完全一样,prepareRecipe()在两者中的实现有所不同。因此,我们可能想到的抽象设计可能是如下:
30417619

进一步封装冲泡法,茶和咖啡的冲泡法都采用了相同的算法。

  • 把水煮沸
  • 用热水泡咖啡或茶
  • 把饮料倒进杯子
  • 在饮料内加入适当的调料
    因此,我们可以将prepareRecipe()也进行抽象。

咖啡因饮料基类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public abstract class CaffeineBeverage {
/**
* 冲泡方法
* 模板算法,定义为final,不可被覆盖
*/
public final void prepareRecipe(){
// 把水煮沸
boilWater();
// 用沸水冲泡
brew();
// 倒进杯子
pourInCup();
// 加调料
addCondiments();
}
private void boilWater() {
System.out.println("把水煮沸");
}
private void pourInCup() {
System.out.println("把咖啡倒进杯子");
}
/**
* 用沸水冲泡
*/
protected abstract void brew();
/**
* 加入调料
*/
protected abstract void addCondiments();
}

Coffee.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Coffee extends CaffeineBeverage {
/**
* 用沸水冲泡
*/
@Override
protected void brew() {
System.out.println("用沸水冲泡咖啡");
}
/**
* 加入调料
*/
@Override
protected void addCondiments() {
System.out.println("加糖加牛奶");
}
}

Tea.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Tea extends CaffeineBeverage {
/**
* 用沸水冲泡
*/
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}
/**
* 加入调料
*/
@Override
protected void addCondiments() {
System.out.println("加柠檬");
}
}

我们做了什么:
32050358

模板方法模式

模板方法定义了一个算法的步骤,并允许子类为一个或多个步骤提供实现。

定义
模板方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
30810299

对模板方法进行挂钩

钩子是一种声明在抽象类中的方法,但只有空的或默认实现。钩子的存在,可以让子类有能力对算法的不同点进行挂钩。要不要挂钩,由子类自行决定。

钩子使用方式一:条件语句
31491858

为了使用钩子,我们要在子类中覆盖它。在这里,钩子控制了咖啡因饮料是否执行某部分算法。说的明确一点,就是饮料中是否要加调料。

QA

  • 创建模板方法时,什么时候用抽象方法,什么时候用钩子?
    当子类必须提供算法中某个方法或步骤的实现时,就使用抽象方法。如果这个步骤是可选的,就用钩子。

  • 使用钩子的目的是什么?

    1. 钩子可以让子类实现算法的可选部分。
    2. 让子类能够有机会对模板方法中某些即将发生的步骤做出反应。

好莱坞原则

别调用我们,我们会调用你。
好莱坞原则可以给我们一种防止“依赖腐败”的方法。当高层组件依赖低层组件,低层组件又依赖高层组件,高层组件又依赖边侧组件,边侧组件又依赖低层组件时,依赖腐败就发生了。
在好莱坞原则下,我们允许低层组件将自己挂钩到系统上,但是高层组件会决定什么时候和怎样使用这些低层组件。换句话说,高层组件对待低层组件的方式是“别调用我,我会调用你”。

源码:https://github.com/chentianming11/design-pattern
template包!