模版方法
原文链接 https://ronghuaxueleng.github.io/2016/10/12/JavaScript%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E4%B8%8E%E5%BC%80%E5%8F%91%E5%AE%9E%E8%B7%B5-13%E6%A8%A1%E7%89%88%E6%96%B9%E6%B3%95/
注:以下为加速网络访问所做的原文缓存,经过重新格式化,可能存在格式方面的问题,或偶有遗漏信息,请以原文为准。
模版方法是一种只需要使用继承就可以实现的非常简单的模式。他由两部分组成,第一部分是抽象父类,第二部分是具体实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些共用方法以及封装子类所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算法结构,并且可以选择重写父类的方法。
Coffee or Tea
假设我们现在要泡一杯咖啡步骤如下:
- 把水煮沸
- 用沸水冲泡咖啡
- 把咖啡倒进杯子
- 加糖和牛奶
泡茶的步骤:
- 把水煮沸
- 用沸水浸泡茶叶
- 把茶水倒进杯子
- 加柠檬
经过比较,我们可以发现泡茶和泡咖啡有以下共同点:
原料不同。一个是茶一个是咖啡,但是我们都可以把它们抽象为“饮料”
泡的方式不同。咖啡是冲泡,茶叶是浸泡,我们都可以抽象成“泡”。
加入的调料不同。一个是糖和牛奶,一个是柠檬,我们把它们抽象成“调料”。
所以经过抽象后,无论是泡咖啡还是泡茶我们都是要进行下列步骤:
- 把水煮沸
- 用沸水冲泡咖啡
- 把饮料倒进杯子
加调料
现在用代码来表示这个抽象过程var Beverage = function() {}; Beverage.prototype.boilWater = function() { console.log('把水煮沸'); }; Beverage.prototype.brew = function(){}; //空方法,应该由子类重写 Beverage.prototype.pourInCup = function(){}; //空方法,应该由子类重写 Beverage.prototype.addCondiments = function(){}; //空方法,应该由子类重写
Beverage.prototype.init= function(){ this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); };
接着就是创建Coffee子类和Tea子类
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类也是类似方法创建,这里不做过多叙述。
在这个案例中,Beverage.prototype.init
就是模版方法,它引导子类以何种顺序去执行哪些方法。
钩子方法
以上模版在正常状况是可以起到很好的作用的,但是,假如有某个客人不喜欢加糖的咖啡,有什么办法可以让子类不受这个约束呢?
这时候我们再对代码做一点改动:
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()) { //如果返回true,则需要调料
this.addCondiments();
}
};
var CoffeeWithHook = function() {};
CoffeeWithHook.prototype = new Beverage();
CoffeeWithHook.prototype.brew = function() {
console.log('用沸水冲泡咖啡');
};
CoffeeWithHook.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
};
CoffeeWithHook.prototype.addCondiments = function() {
console.log('加糖加牛奶');
};
CoffeeWithHook.prototype.customerWantsCondiments = funtion() {
return window.confirm('请问需要调料吗?');
}
var coffeeWithHook = new CoffeeWithHook();
coffeeWithHook.init();