[TOC]
# 模板方法模式
模板方法模式是一种只需使用继承就可以实现的非常简单的模式。
模板方法模式由两部分构成:
- 抽象父类:封装了子类的算法框架,包括实现一些公共方法以及封装子类中所有方法的执行顺序。
- 具体实现的子类:通过基础抽象父类,也继承了整个算法结构,可以选择重写父类的方法。
假如一些平行的子类,各个子类直接有一些相同的行为,也有一些不同的行为。相同的行为可能会重复出现在各个子类之间,那这些相同的行为可以被搬到另一个单一的地方,模板方法模式就是为了解决这个问题而生的。
```javascript
var Beverage = function();
Beverage.prototype.boilWater = function(){ // 相同的方法
...
};
Beverage.prototype.brew = function(){ // 空方法,由子类进行重写
...
};
Beverage.prototype.pourInCup = function(){ // 空方法,由子类进行重写
...
};
Beverage.prototype.addCondiments = function(){ // 空方法,由子类进行重写
...
};
Beverage.prototype.init = function(){
this.boilWater();
this.brew();
this.pourInCup();
this.addCondiments();
};
// 创建子类
var Coffee = new Beverage();
Coffee.prototype.brew = function(){
...
};
Coffee.prototype.pourInCup = function(){
...
};
Coffee.prototype.addCondiments = function(){
...
}
var coffee = new Coffee();
conffee.init();
```
在上面的例子中,`Beverage.prototype.init`才是模板方法模式。该方法封装了子类的算法框架,它作为一个算法模板,指导子类以何种顺序去执行哪些方法。
## 抽象类
模板方法模式是一种严重依赖抽象类的设计模式。在`Java`中有两种类,一种是具体类,一种是抽象类。具体类可以被实例化,抽象类不能被实例化。就像说:我要一杯饮料,但是随后还会被问到要什么饮料。而是要具体说咖啡。
抽象方法被声明在抽象类中,抽象方法并没有具体的实现过程,是一些“哑”方法。当子类继承了这个抽象类时,必须重写父类的抽象方法。
如果每个子类中都有一些同样的具体实现方法,那这些方法也可以选择放在抽象类中,这些方法就被称为具体方法。
## JavaScript没有抽象类的缺点和解决方案
Java中编译器会保证子类会重写父类中的抽象方法,JavaScript中却没有进行这些检查工作。编写代码时得不到任何形式的警告,完全寄托于程序员的记忆力和自觉性是很危险的。
有两种解决方案
- 用鸭子类型来模拟接口类型检查,以确保子类中确实重写了父类的方法。但会带来不必要的复杂性。
- 抽象父类中的方法直接抛出异常。实现简单,付出的额外代价很少。但是得到错误信息时间太靠后。
```javascript
Beverage.prototype.brew = function(){ // 相同的方法
throw new Error('子类必须重写brew方法')
};
```
## 使用场景
大的方面就是架构师用于搭建项目的框架,程序员继承框架开始填空。
比如在Web开发中的UI组件。(1)初始化一个div容器;(2)通过ajax请求拉取相应的数据;(3)把数据渲染到div容器里面,完成组件的构造;(4)通知用户组件渲染完成。任何组件的构建(1)(4)相同。把4个方法都抽象到父类的模板方法,提供(1)(4)的具体实现。子类继承后,重写(2)(3);
## 钩子方法
隔离变化的常见手段。
## 好莱坞原则
允许底层组件将自己挂钩到高层组件中,而高层组件会决定什么时候、以何种方式去使用这些底层组件。也就是“别调用我们,我们会调用你”。
当我们用模板方法模式编写一个程序时,就意味着子类放弃了对自己的控制权,而是改为父类通知子类,哪些方法应该在什么时候被调用。作为子类只负责一些设计上的细节。
## 真的需要继承吗
```javascript
var Beverage = function(param){
var boilWater = function(){...};
var brew = param.brew || function(){...};
var pourInCup = param.pourInCup || function(){...};
var F = function(){};
F.prototype.init = function(){
boilWater();
brew();
pourInCup();
}
return F;
};
// 创建子类
var Coffee = Beverage({
brew: function(){...},// 重写
pourInCup: function(){...}
});
var coffee = new Coffee();
coffee.init();
```
该模板方法依然封装了子类的算法框架。