发布于 

设计模式之模板方法模式

模板方法模式:由两部分结构组成,第一部分是抽象父类,第二部分是具体实现的子类。在抽象父类中封装了子类的算法框架,子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类方法。

应用场景

假如我们有一些平行的子类,各个子类之间有一些相同的行为,也有一些不同的行为,像这种情况就可以使用模板方法模式。在模板方法模式中,子类实现中的相同部分被上移到父类中,而将不同的部分留给子类自己实现

例子-咖啡与茶

泡咖啡的步骤通常如下:

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

泡茶的步骤如下:

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

其实泡茶的步骤跟泡咖啡的步骤相差并不大,冲泡过程是大同小异的,找出两者的不同点,并将不同点转化为相同点:

  • 咖啡、茶相当于冲泡的原料不同,但我们可以把它们抽象为”饮料“

  • 冲泡、浸泡这是不同的动作,但我们可以把它们抽象为”泡“

  • 糖和牛奶、柠檬相当于加入的调料不同,但我们可以把它们都抽象为”调料“

经过抽象之后,不管是泡咖啡还是泡茶,我们都能整理为以下四步:

  1. 把水煮沸
  2. 用沸水冲泡饮料
  3. 把饮料倒进杯子
  4. 加调料

具体代码实现:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//抽象父类
abstract class Beverage{
boilWater(){
console.log('把水煮沸')
}
abstract brew():void //空方法,由子类重写
abstract pourInCup():void //空方法,由子类重写
abstract addCondiments():void //空方法,由子类重写
init(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
}

}
//创建咖啡类
class Coffee extends Beverage{
constructor(){
super()
}
brew(): void {
console.log('用沸水冲泡咖啡')
}
pourInCup(): void {
console.log('把咖啡倒进杯子')
}
addCondiments(): void {
console.log('加糖和牛奶')
}
}
//创建茶类
class Tea extends Beverage{
constructor(){
super()
}
brew(): void {
console.log('用水冲泡茶叶')
}
pourInCup(): void {
console.log('把茶倒进杯子')
}
addCondiments(): void {
console.log('加柠檬')
}
}

const coffee = new Coffee();
coffee.init()
const tea = new Tea()
tea.init()

钩子方法

在上述步骤中,我们规定了咖啡的制作为4个步骤。但有些客人喝咖啡是不加调料(糖和牛奶)的,这种情况又该怎么办?

钩子方法可以用来解决这个问题,放置钩子是隔离变化的一种常见手段。我们在父类中容易变化的地方放置钩子,钩子可以有一个默认的实现,究竟要不要”挂钩“,这由子类自行决定。钩子方法的返回结果决定了模板方法后面部分的执行步骤,也就是程序接下来的走向,这样一来,程序就拥有了变化的可能

加不加调料由客人决定

具体代码实现:

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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//挂钩
abstract class Beverage{
abstract brew():void
abstract pourInCup():void
abstract addCondiments():void

boilWater(){
console.log('把水煮沸')
}
customerWantCondiments(){ //默认需要调料,由子类自行决定
return true
}

init(){
this.boilWater();
this.brew();
this.pourInCup();
if(this.customerWantCondiments()){ //如果需要调料,就加调料
this.addCondiments();
}

}

}
//创建咖啡类
class Coffee extends Beverage{
constructor(){
super()
}
brew(): void {
console.log('用沸水冲泡咖啡')
}
pourInCup(): void {
console.log('把咖啡倒进杯子')
}
addCondiments(): void {
console.log('加糖和牛奶')
}
customerWantCondiments(){
return window.confirm('请问需要调料吗?')
}
}

const coffee = new Coffee();
coffee.init()