模板方法模式是一种只需要使用继承就可以实现的非常简单的模式。
1.定义
模板方法模式由两部分组成,第一部分是抽象父类,第二部分是具体的实现子类。通常在抽象父类中封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。子类通过继承这个抽象类,也继承了整个算结构,并且可以选择重写父类的方法。
2.模板方法模式的基本实现
下面以一个咖啡和茶的例子进行演示,首先泡一杯咖啡
/**
* 泡一杯咖啡的步骤为:
* (1)把水煮沸
* (2)用沸水冲泡咖啡
* (3)把咖啡倒进杯子
* (4)加糖和牛奶
*/
let Coffee = function () { };
Coffee.prototype.boilWater = function () {
console.log('把水煮沸');
}
Coffee.prototype.brewCoffeeGriends = function () {
console.log('用沸水冲泡咖啡');
}
Coffee.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
}
Coffee.prototype.addSugerAndMilk = function () {
console.log('加糖和牛奶');
}
Coffee.prototype.init = function () {
this.boilWater();
this.brewCoffeeGriends();
this.pourInCup();
this.addSugerAndMilk();
}
let coffee = new Coffee();
coffee.init();
然后泡一壶茶
/**
* 泡一壶茶的步骤为:
* (1)把水煮沸
* (2)用沸水浸泡茶叶
* (3)把茶水倒进杯子
* (4)加柠檬
*/
let Tea = function () { };
Tea.prototype.boilWater = function () {
console.log('把水煮沸');
}
Tea.prototype.steepTeaBag = function () {
console.log('用沸水浸泡茶叶');
}
Tea.prototype.pourInCup = function() {
console.log('把茶水倒进杯子');
}
Tea.prototype.addLemon = function () {
console.log('加柠檬');
}
Tea.prototype.init = function () {
this.boilWater();
this.steepTeaBag();
this.pourInCup();
this.addLemon();
}
let tea = new Tea();
tea.init();
将它们之间的共同点进行抽象,得出以下几点:
- 原料不同,分别为茶和咖啡,在这里可以抽象为冲泡品
- 泡的方式不同,在这里统一抽象为冲泡
- 加的调味料不同,在这里统一抽象为加调味料
最后将这些行为统一抽象为以下代码:
/**
* 泡一杯饮品的步骤为:
* (1)把水煮沸
* (2)冲泡饮品
* (3)把饮品倒进杯子
* (4)加调料
*/
let 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.steepBeverageBag();
this.pourInCup();
this.addLemon();
}
最后,泡一壶咖啡或茶的代码为:
let Coffee = function () { };
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function () {
console.log('用沸水冲泡咖啡');
}
Coffee.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
}
Coffee.prototype.addCondiments = function () {
console.log('加糖和牛奶');
}
let coffee = new Coffee();
coffee.init();
/**
* 在以上代码中,Coffee作为子类,继承了Beverage的原型,在Coffee子类中,重写了
* Beverage中的三个空方法,当调用coffee.init()时,Coffee子类本身没有init方
* 法,因此会在原型链中寻找该方法,因此最终调用的是Beverage.init();
*/
ps.泡茶和咖啡的步骤一致,这里就不多做赘述了。
3.JavaScript没有抽象类的缺点和解决方案
因为JavaScript中并没有抽象类的概念,因此它不会对子类有没有进行父类抽象方法的重写进行检查,因此我们只能够在写代码的过程中进行检查或者使用以下两种方法:
(1)用鸭子类型来模拟接口检查,以确保子类中确实重写了父类的方法。
(2)直接在模板方法中抛出一个异常,在运行程序的时候会得到一个错误。
let 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('子类必须重写addCondimentsw方法');
}
4.钩子方法
在前面咖啡和茶的例子中,有一个加调料的步骤,但在实际场景中,调料通常是随喝的人的意愿选择加或不加的,因此在这里我们可以使用勾子方法来将添加调料这一过程变成自由选择的。
let 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('子类必须重写addCondimentsw方法');
}
Beverage.prototype.isAddCondiments = function () { //是否添加调料方法,true表示添加,false表示不添加
return true; //默认添加
}
Beverage.prototype.init = function () {
this.boilWater();
this.brew();
this.pourInCup();
if (this.isAddCondiments()) {
this.addCondiments();
}
}
let Coffee = function () { };
Coffee.prototype = new Beverage();
Coffee.prototype.brew = function () {
console.log('用沸水冲泡咖啡');
}
Coffee.prototype.pourInCup = function() {
console.log('把咖啡倒进杯子');
}
Coffee.prototype.addCondiments = function () {
console.log('加糖和牛奶');
}
Coffee.prototype.isAddCondiments() = function () {
return window.confirm('请问需要调料吗?');
}
let coffee = new Coffee();
coffee.init();
5.好莱坞原则
允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件。
6.真的需要“继承”吗
在前面实现的代码中,我们都是通过JavaScript模仿其他语言的继承操作来实现的模板方法,但在好莱坞原则的指导下,我们实际上可以可以这样来实现一个模板方法
let Beverage = function (parm) {
let boilWater = function () {
console.log('把水煮沸');
}
let brew = parm.brew || function () {
return new Error('必须传递brew方法');
}
let pourInCup = parm.pourInCup || function () {
return new Error('必须传递pourInCup方法');
}
let addCondiments = parm.addCondiments || function () {
return new Error('必须传递addCondiments');
}
let F = function () { }
F.prototype.init = function () {
boilWater();
brew();
pourInCup();
addCondiments();
}
return F;
}
let Coffee = Beverage({
brew: function () {
console.log('用沸水冲泡咖啡');
},
pourInCup: function () {
console.log('把咖啡倒进杯子');
},
addCondiments: function () {
console.log('加糖和牛奶');
}
})
let Tea = Beverage({
brew: function () {
console.log('用沸水浸泡茶叶');
},
pourInCup: function () {
console.log('把茶倒进杯子');
},
addCondiments: function () {
console.log('加柠檬');
}
})
let coffee = new Coffee();
coffee.init();
let tea = new Tea();
tea.init();
最终,在好莱坞原则的指导下,以JavaScript形式完成了模板方法的实现。