模板方法模式是一种只需要使用继承就可以实现的非常简单的模式。
模板方法由两部分结构组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架以及子类中所有方法的执行顺序,子类通过继承也就继承了整个算法结构。
例子
对比一下泡茶和泡咖啡的步骤:
泡咖啡 | 泡茶 |
---|---|
把水煮沸 | 把水煮沸 |
用沸水冲泡咖啡 | 用沸水冲泡茶叶 |
把咖啡倒进杯子 | 把茶水倒进杯子 |
加糖和牛奶 | 加柠檬 |
两者的流程基本相似,只是材料、泡的方式不同;经抽象我们把泡茶泡咖啡整理为下面四步:
- 把水煮沸
- 用沸水冲泡饮料
- 把饮料倒进杯子
- 加调料
有可能有人比喜欢加调料,因此引入钩子方法实现自定制。
var Beverage = function(){};
Beverage.prototype.boilWater = function(){
console.log("把水煮沸");
}
Beverage.prototype.brew = function(){
throw new Error("子类必须重写brew方法");
}
Beverage.prototype.pourInCup = function(){
throw new Error("子类必须重写pourInCup方法");
}
Beverage.prototype.addCondiments = function(){
throw new Error("子类必须重写addCondiments方法");
}
Beverage.prototype.customerWantsCondiments = function(){ return true;} // 默认需要调料
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
if( this.customerWantsCondiments() ){ // 钩子
this.addCondiments();
}
}
// coffee
var Coffee = function(){}
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function(){
console.log("用沸水冲泡咖啡");
}
Coffee.prototype.pourInCup = function(){
console.log("把咖啡倒进杯子");
}
Coffee.prototype.addCondiments = function(){
console.log("加糖和牛奶");
}
var Coffee = new Coffee();
Coffee.init();
// Tea
var Tea = function(){}
Tea.prototype = new Beverage();
Tea.prototype.brew = function(){
console.log("用沸水浸泡茶叶");
}
Tea.prototype.pourInCup = function(){
console.log("把茶倒进杯子");
}
Tea.prototype.customerWantsCondiments = function(){return false;}
Tea.prototype.addCondiments = function(){
console.log("加柠檬");
}
var Tea = new Tea();
Tea.init();
由于js没有从语法成名提供对抽象类的支持,也没有编译器帮助我们检查,因此需要我们通过在父类方法中抛出异常来提示子类需要重写此方法。
好莱坞原则
新演员为了找到工作,只能把简历递给演艺公司然后回家等电话。当有工作机会时,演艺公司会通知演员;而如果演员主动联系演艺公司,往往得到的答复是:“不要来找我,我会给你打电话”。
设计模式中,这样的规则成为好莱坞原则。在这一原则的指导下,我们允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件;高层组件对待底层组件的方式,跟演艺公司对待新人演员一样,都是:“别动用我们,我们会调用你”。
模板方法模式是好莱坞原则的一个典型使用场景:用模板方法模式编写程序,其中的子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该什么时候调用;作为子类只负责细节上的实现。
发布-订阅者模式以及回调函数也是好莱坞原则的典型使用场景。