《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     };

 

posted @ 2017-02-07 11:32  出世Sunny  阅读(196)  评论(0编辑  收藏  举报