《javascript设计模式与开发实践》阅读笔记(11)—— 模板方法模式
模板方法模式:
由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
泡咖啡和泡茶的例子
1 var Coffee = function(){}; //作为构造函数
2 Coffee.prototype.boilWater = function(){
3 console.log( '把水煮沸' );
4 };
5 Coffee.prototype.brewCoffeeGriends = function(){
6 console.log( '用沸水冲泡咖啡' );
7 };
8 Coffee.prototype.pourInCup = function(){
9 console.log( '把咖啡倒进杯子' );
10 };
11 Coffee.prototype.addSugarAndMilk = function(){
12 console.log( '加糖和牛奶' );
13 };
14
15 Coffee.prototype.init = function(){
16 this.boilWater(); //煮水
17 this.brewCoffeeGriends(); //泡咖啡
18 this.pourInCup(); //倒进杯子
19 this.addSugarAndMilk(); //加糖和牛奶
20 };
21
22 var coffee = new Coffee(); //实例一个“咖啡”对象
23 coffee.init(); //调用
24
25
26 var Tea = function(){}; //作为构造函数
27 Tea.prototype.boilWater = function(){
28 console.log( '把水煮沸' );
29 };
30 Tea.prototype.steepTeaBag = function(){
31 console.log( '用沸水浸泡茶叶' );
32 };
33 Tea.prototype.pourInCup = function(){
34 console.log( '把茶水倒进杯子' );
35 };
36 Tea.prototype.addLemon = function(){
37 console.log( '加柠檬' );
38 };
39
40 Tea.prototype.init = function(){
41 this.boilWater(); //煮水
42 this.steepTeaBag(); //泡茶
43 this.pourInCup(); //倒进杯子
44 this.addLemon(); //加柠檬
45 };
46
47 var tea = new Tea(); //实例一个“茶”对象
48 tea.init(); //调用
观察两段代码,发现其实他们是大同小异的,那我们完全可以把公共的部分抽象出来,作为一个抽象的父类模板
1 var drink=function(){} //作为抽象的构造类
2 drink.prototype.boilWater=function(){
3 console.log( '把水煮沸' );
4 }
5 drink.prototype.brew=function(){}; //空方法,用来给模板套用
6 drink.prototype.pourInCup = function(){}; // 同上
7 drink.prototype.addCondiments = function(){}; // 同上
8
9 drink.prototype.init = function(){ //模板在这里,重复的事情放在模板里
10 this.boilWater();
11 this.brew();
12 this.pourInCup();
13 this.addCondiments();
14 };
然后就是具体的咖啡类和茶类
1 /**创建咖啡类**/
2 var Coffee = function(){}; //这是一个具体的类,作为构造函数存在
3 Coffee.prototype = new drink(); //把构造函数的prototype指向抽象类的实例
4
5 Coffee.prototype.brew = function(){
6 console.log( '用沸水冲泡咖啡' );
7 };
8 Coffee.prototype.pourInCup = function(){
9 console.log( '把咖啡倒进杯子' );
10 };
11 Coffee.prototype.addCondiments = function(){
12 console.log( '加糖和牛奶' );
13 };
14
15 var coffee = new Coffee(); //新建咖啡实例
16 coffee.init(); //调用模板
17 /*结果*/
18 //把水煮沸
19 //用沸水冲泡咖啡
20 //把咖啡倒进杯子
21 //加糖和牛奶
1 /**创建茶类**/
2 var Tea = function(){}; //具体的类,作为构造函数
3 Tea.prototype = new drink(); //把构造函数的prototype指向抽象类的实例
4
5 Tea.prototype.brew = function(){
6 console.log( '用沸水浸泡茶叶' );
7 };
8 Tea.prototype.pourInCup = function(){
9 console.log( '把茶倒进杯子' );
10 };
11 Tea.prototype.addCondiments = function(){
12 console.log( '加柠檬' );
13 };
14
15 var tea = new Tea(); //新建茶实例
16 tea.init(); //调用模板
17 /*结果*/
18 //把水煮沸
19 //用沸水浸泡茶叶
20 //把茶倒进杯子
21 //加柠檬
使用es6的话,代码可以简洁不少
1 class drink{ //抽象模板父类
2 boilWater(){
3 console.log("把水煮沸");
4 };
5 brew(){};
6 pourInCup(){};
7 addCondiments(){};
8
9 init(){
10 this.boilWater();
11 this.brew();
12 this.pourInCup();
13 this.addCondiments();
14 }
15 }
16
17 class Coffee extends drink{ //具体的类,内部重写相应方法
18 brew(){
19 console.log("用沸水冲泡咖啡");
20 }
21 pourInCup(){
22 console.log( '把咖啡倒进杯子' );
23 }
24 addCondiments(){
25 console.log( '加糖和牛奶' );
26 }
27 }
28
29 var coffee=new Coffee(); //实例
30 coffee.init(); //把水煮沸
31 //用沸水冲泡咖啡
32 //把咖啡倒进杯子
33 //加糖和牛奶
js实现模板模式的一些问题
我们在抽象父类中提供了模板,在具体的类中重写相应的方法,但是这个过程全靠程序员的自觉和记忆,语言层面并没有提供任何检查,如果我们忘了重写相应的方法,js也不会报错。
一种解决方案是用鸭子类型来模拟接口检查,缺点就是会带来很多不必要的复杂性,增加很多和业务无关的代码。
另一种解决方案是抽象父类中的相应抽象方法里都抛出错误,如果子类没有重写相应方法,运行时就会报错,如下:
1 drink.prototype.brew=function(){
2 throw new Error( '子类必须重写brew 方法' );
3 };
钩子方法
可以增加自由度,有的子类并不适合把模板方法全部运行,钩子方法可以让子类自行决定是否执行对应的模板方法。
1 drink.prototype.add=function(){ //钩子方法,子类中可以改写,模板方法中会进行判断
2 return true; //默认为true,用于判断
3 }
4 drink.prototype.init = function(){ //模板方法
5 this.boilWater();
6 this.brew();
7 this.pourInCup();
8 if( this.add() ){ //如果挂钩返回true,则需要调料
9 this.addCondiments();
10 }
11 };