设计模式系列-模板方法模式

JAVA设计模式系列:

模板方法模式

定义

模板方法模式在一个方法中定义了算法的骨架,把其中的某些步骤延迟到子类的实现,是为我们提供了代码复用的一种重要的技巧。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

实现

这里简单通过一个示例来展示到底什么时候模板方法模式。这个示例向我们展示了制作咖啡和茶2种咖啡因饮料的过程,在这个过程中展示了模板方法模式的具体使用方法。

代码地址:GitHub

先看一下模板方法模式的类图:

首先我们定义一个抽象类CaffeineBeverage来作为模板方法的基类。具体代码如下:

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
public abstract class CaffeineBeverage {
// 模板方法
final void prepareReipe() {
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");
}

CaffeineBeverage类中定义了一个名为prepareReipe()的模板方法,用来描述冲泡咖啡因饮料的过程。方法用final修饰是为了防止子类修改方法的执行顺序。

CaffeineBeverage类定义了4个方法,分别是brew()addCondiments()boilWater()pourInCup()。在我们的示例中,冲泡咖啡和茶共有的过程分别是煮水 boilWater()倒进杯子里 pourInCup()。这两个共用方法选择在CaffeineBeverage类实现。

Tea类、Coffee类是CaffeineBeverage类的子类。而加料 addCondiments()浸泡 brew()分别在Tea类、Coffee类中有各自不同的实现。如下所示:

1
2
3
4
5
6
7
8
9
10
public class Tea extends CaffeineBeverage {
void brew() {
System.out.println("Stepping the tea.");
}
void addCondiments() {
System.out.println("Adding Lemon");
}
}
1
2
3
4
5
6
7
8
9
10
public class Coffee extends CaffeineBeverage {
void brew() {
System.out.println("Dripping Coffee through filter");
}
void addCondiments() {
System.out.println("Adding Suger and Mike");
}
}

完成了模板方法模式的代码,我们可以进行测试一下,测试类:

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void main(String[] args) {
Tea tea = new Tea();
tea.prepareReipe();
System.out.println("**************");
Coffee coffee = new Coffee();
coffee.prepareReipe();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
Boiling water
Stepping the tea.
Pouring into cup
Adding Lemon
**************
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Suger and Mike

我们将冲茶和咖啡重复的方法煮水 boilWater()倒进杯子里 pourInCup()抽象出来,每个子类分别去实现各自特有的步骤。以上便是模板方法的实例。

钩子

还需了解到,模板方法模式还有钩子的概念。钩子是一种被声明在抽象类的方法,可以为空或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,是否需要挂钩由子类决定。

借助上面的示例来展示钩子如何使用。首先我们在抽象类CaffeineBeverage定一个钩子,钩子的默认实现返回true。如下:

1
2
3
4
// 定义一个钩子
boolean customerWantsCondiments() {
return true;
}

并修改模板方法:

1
2
3
4
5
6
7
8
9
// 模板方法
final void prepareReipe() {
boilWater();
brew();
pourInCup();
if (customerWantsCondiments()) {
addCondiments();
}
}

目的是增加让客户选择是否需要给茶或者饮料来添加东西。我们可以在子类中覆盖钩子的写法。这里改下下Tea类,如下:

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 extends CaffeineBeverage {
private String msg;
public Tea(String msg) {
this.msg = msg;
}
void brew() {
System.out.println("Stepping the tea.");
}
void addCondiments() {
System.out.println("Adding Lemon");
}
boolean customerWantsCondiments() {
if ("y".equals(this.msg)) {
return true;
} else {
return false;
}
}
}

添加了一个msg变量,可以通过构造函数进行赋值,当msgy时候,我们将在茶里添加柠檬,否则不添加。看一下测试代码:

1
2
3
4
5
6
7
8
9
public static void main(String[] args) {
Tea tea = new Tea("n");
tea.prepareReipe();
System.out.println("**************");
Coffee coffee = new Coffee();
coffee.prepareReipe();
}

运行结果:

1
2
3
4
5
6
7
8
Boiling water
Stepping the tea.
Pouring into cup
**************
Boiling water
Dripping Coffee through filter
Pouring into cup
Adding Suger and Mike

和上面的比较一下,发现制作茶的过程中缺少了添加东西的过程,主要是因为我们在Tea类,重写了钩子,来控制加料的步骤。

代码地址:GitHub

如有纰漏,烦请指出。

参考《Head First 设计模式》