模版方法模式在一个方法中定义了一个算法的骨架,而将一些步骤延迟到子类中。模版方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤
实际上模版方法模式很简单,就是单纯的利用了子类可以继承父类的方法的这一特性。
下面以一个咖啡和茶的例子来说明这个问题
假设我们泡一杯咖啡有下面的四个步骤
- 煮开水
- 冲泡咖啡
- 把泡好的咖啡放进倒进杯子里
- 加糖和牛奶
泡茶也是同样的四个步骤
- 煮开水
- 冲泡茶包
- 把泡好的茶放进倒进杯子里
- 加柠檬
如果我们要实现上面的功能,正常情况下可能需要实现两个类,一个coffee,一个tea,但是这两个类中有共同的部分,都需要煮开水,都需要倒进杯子里。那么自然的我们就想到要抽象出一个父类,然后coffee/tea基层自父类就好了啊。
因此我们有如下的设计方案:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16public abstract class CaffeineBeverage {
final void prepare() {
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!");
}
}
1 | public class Coffee extends CaffeineBeverage { |
1 | public class TemplatePatternTest { |
下面我们看下我们的抽象类CaffeineBeverage, 它有一个final的prepare方法,这是因为我们不想让子类来覆盖这个方法,这样防止了被篡改。
他定义了两个abstract的方法,这两个方法需要子类自己去实现,这样才能让我们作出coffee和tea两种不同的饮料。
下面的这张图显示了我们应该怎样定义模板方法模式
对模板方法进行挂钩子
钩子是一中被声明在抽象类中的方法,但是只有空的或者默认的实现。钩子的存在可以让子类有能力对算法的不同点进行挂钩,而要不要挂钩由子类决定。
还是上面的例子,假设在我们的饮料中加不加糖由子类自己决定,那么该怎样处理呢?
我们可以新创建一个CoffeeWithoutSugar的类,这个类中addCondiments可以空实现,这样就可以解决,但是这样的方案好么?这样会导致类爆炸。
我们可以用钩子来解决。
1 | public abstract class CaffeineBeverageWithHook { |
这个就是钩子。实际上我们在相应的位置出加上判断函数(这个bool函数实际上就是钩子),子类需要实现这个钩子,进而能够影响到父类中的算法实现。
1 | boolean customerWantsCondiments() { |
相应的子类实现如下:
1 | public class CoffeeWithHook extends CaffeineBeverageWithHook { |
下面引入一个新的设计原则
好莱坞原则:别调用我们,我们会调用你
好莱坞原则的引入是为了防止“依赖腐败”。在某些场景下高层组件会依赖低层组件,低层组件又依赖高层组件,而高层组件又依赖边层组件,边层组件又依赖低层组件,这样组件间就会导致依赖腐败,进而系统变得越来越复杂。
而在好莱坞原则下,允许低层组件挂钩到系统上,高层组件会决定什么时候使用这些低层组件。实际上高层组件对低层组件的方式是“别调用我们,我们会调用你”
我们的CoffeineBeverage就是高层组件,它控制了冲泡的算法,只有在需要子类实现的算法时,才会调用子类。
代码归档路径
https://github.com/changyuanchn/DesignPattern/tree/main/src/com/changyuan/TemplatePattern