[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();
```
該模板方法依然封裝了子類的算法框架。