模板方法模式

在模板模式(Template Pattern)中,一个抽象类公开定义了执行它的方法的方式/模板。它的子类可以按需要重写方法实现,但调用将以抽象类中定义的方式进行

Coffee or Tea

我们现在需要冲泡一杯咖啡和一壶茶,它们的操作步骤基本上是一样的,如下所示

泡咖啡 泡茶
把水煮沸 把水煮沸
用沸水冲泡咖啡 用沸水浸泡茶叶
咖啡倒进杯子 茶水倒进杯子
加糖和牛奶 加柠檬

基本的步骤是一样的,我们可以使用抽象类的形式,把步骤框架抽象出来,Coffee类和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
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
// 创建一个抽象父类来表示泡一杯饮料的整个过程。
// 不论是 Coffee,还是 Tea,都被我们用Beverage来表示
class Beverage {
boilWater() {
console.log('把水煮沸');
};

brew() {
throw new Error('子类必须重写 brew 方法');
};

pourInCup() {
throw new Error('子类必须重写 pourInCup 方法');
};

addCondiments() {
throw new Error('子类必须重写 addCondiments 方法');
};

init() { // 模板方法
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};
}

// 创建Coffee类
class Coffee extends Beverage {
brew() {
console.log('用沸水冲泡咖啡');
};

pourInCup() {
console.log('把咖啡倒进杯子');
};

addCondiments() {
console.log('加糖和牛奶');
};
}

let coffee = new Coffee();
coffee.init();
// 把水煮沸
// 用沸水冲泡咖啡
// 把咖啡倒进杯子
// 加糖和牛奶


// 创建Tea类
class Tea extends Beverage {
brew() {
console.log('用沸水浸泡茶叶');
};

pourInCup() {
console.log('把茶倒进杯子');
};

addCondiments() {
console.log('加柠檬');
};
}

let tea = new Tea();
tea.init();
// 把水煮沸
// 用沸水浸泡茶叶
// 把茶倒进杯子
// 加柠檬

上面代码中,Beverage父类里的init方法即为模板方法,init封装了子类的算法框架,作为算法的模板,指导子类以何种顺序去执行哪些方法。

好莱坞原则

​ 好莱坞无疑是演员的天堂,但好莱坞也有很多找不到工作的新人演员,许多新人演员在好莱坞把简历递给演艺公司之后就只有回家等待电话。有时候该演员等得不耐烦了,给演艺公司打电话询问情况,演艺公司往往这样回答:“不要来找我,我会给你打电话。” 在设计中,这样的规则就称为好莱坞原则。在这一原则的指导下,我们允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件,高层组件对 待底层组件的方式,跟演艺公司对待新人演员一样,都是“别调用我们,我们会调用你”。
​ 比如发布订阅模式和回调函数

是否真的需要继承

在JavaScript这种灵活的语言中,我们是否真的需要使用继承这种重武器呢。在好莱坞原则的指导下,我们可以重构上面的代码

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
let Beverage = function (param) {
let boilWater = function () {
console.log('把水煮沸');
};
let brew = param.brew || function () {
throw new Error('必须传递 brew 方法');
};
let pourInCup = param.pourInCup || function () {
throw new Error('必须传递 pourInCup 方法');
};
let addCondiments = param.addCondiments || function () {
throw new Error('必须传递 addCondiments 方法');
};
let F = function () {};
F.prototype.init = function () {
boilWater();
brew();
pourInCup();
addCondiments();
};
return F;
};

let Coffee = Beverage({
brew() {
console.log('用沸水冲泡咖啡');
},
pourInCup() {
console.log('把咖啡倒进杯子');
},
addCondiments() {
console.log('加糖和牛奶');
}
});

let Tea = Beverage({
brew() {
console.log('用沸水浸泡茶叶');
},
pourInCup() {
console.log('把茶倒进杯子');
},
addCondiments() {
console.log('加柠檬');
}
});

总结

​ 模板方法模式是一种典型的通过封装变化提高系统扩展性的设计模式。在传统的面向对象语言中,一个运用了模板方法模式的程序中,子类的方法种类和执行顺序都是不变的,所以我们把这部分逻辑抽象到父类的模板方法里面。而子类的方法具体怎么实现则是可变的,于是我们把这部分变化的逻辑封装到子类中。通过增加新的子类,我们便能给系统增加新的功能,并不需要改动抽象父类以及其他子类,这也是符合开放-封闭原则的。

参考

《javascript设计模式与开发实践》

作者

Liang

发布于

2021-02-05

更新于

2021-12-25

许可协议


评论