模板方法模式是一種只需使用繼承就能夠實現的很是簡單的模式。
模板方法模式由兩部分結構組成,第一部分是抽象父類,第二部分是具體的實現子類。一般在抽象父類中封裝了子類的算法框架,包括實現一些公共方法以及封裝子類中全部方法的執行順
序。子類經過繼承這個抽象類,也繼承了整個算法結構,而且能夠選擇重寫父類的方法。javascript
假如咱們有一些平行的子類,各個子類之間有一些相同的行爲,也有一些不一樣的行爲。若是相同和不一樣的行爲都混合在各個子類的實現中,說明這些相同的行爲會在各個子類中重複出現。但實際上,相同的行爲能夠被搬移到另一個單一的地方,模板方法模式就是爲解決這個問題而生的。在模板方法模式中,子類實現中的相同部分被上移到父類中,而將不一樣的部分留待子類來實現。這也很好地體現了泛化的思想。html
首先,咱們先來泡一杯咖啡,若是沒有什麼太個性化的需求,泡咖啡的步驟一般以下:
(1) 把水煮沸
(2) 用沸水沖泡咖啡
(3) 把咖啡倒進杯子
(4) 加糖和牛奶
經過下面這段代碼,咱們就能獲得一杯香濃的咖啡:java
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> var Coffee = function(){}; Coffee.prototype.boilWater = function(){ console.log( '把水煮沸' ); }; Coffee.prototype.brewCoffeeGriends = function(){ console.log( '用沸水沖泡咖啡' ); }; Coffee.prototype.pourInCup = function(){ console.log( '把咖啡倒進杯子' ); }; Coffee.prototype.addSugarAndMilk = function(){ console.log( '加糖和牛奶' ); }; Coffee.prototype.init = function(){ this.boilWater(); this.brewCoffeeGriends(); this.pourInCup(); this.addSugarAndMilk(); }; var coffee = new Coffee(); coffee.init(); </script> </body> </html>
接下來,開始準備咱們的茶,泡茶的步驟跟泡咖啡的步驟相差並不大:
(1) 把水煮沸
(2) 用沸水浸泡茶葉
(3) 把茶水倒進杯子
(4) 加檸檬
一樣用一段代碼來實現泡茶的步驟:ajax
var 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(); }; var tea = new Tea(); tea.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.init = function(){ this.boilWater(); this.brew(); this.pourInCup(); this.addCondiments(); };
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> 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(); }; 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(); </script> </body> </html>
本章一直討論的是模板方法模式,那麼在上面的例子中,到底誰纔是所謂的模板方法呢?答案是 Beverage.prototype.init 。算法
Beverage.prototype.init 被稱爲模板方法的緣由是,該方法中封裝了子類的算法框架,它做爲一個算法的模板,指導子類以何種順序去執行哪些方法。在 Beverage.prototype.init 方法中,
算法內的每個步驟都清楚地展現在咱們眼前。框架
在 Web開發中也能找到不少模板方法模式的適用場景,好比咱們在構建一系列的 UI組件,
這些組件的構建過程通常以下所示:
(1) 初始化一個 div容器;
(2) 經過 ajax請求拉取相應的數據;
(3) 把數據渲染到 div容器裏面,完成組件的構造;
(4) 通知用戶組件渲染完畢。this
咱們看到,任何組件的構建都遵循上面的 4 步,其中第(1)步和第(4)步是相同的。第(2)步不一樣的地方只是請求 ajax的遠程地址,第(3)步不一樣的地方是渲染數據的方式。spa
因而咱們能夠把這 4個步驟都抽象到父類的模板方法裏面,父類中還能夠順便提供第(1)步和第(4)步的具體實現。當子類繼承這個父類以後,會重寫模板方法裏面的第(2)步和第(3)步。prototype
經過模板方法模式,咱們在父類中封裝了子類的算法框架。這些算法框架在正常狀態下是適用於大多數子類的,但若是有一些特別「個性」的子類呢?好比咱們在飲料類 Beverage 中封裝了
飲料的沖泡順序:
(1) 把水煮沸
(2) 用沸水沖泡飲料
(3) 把飲料倒進杯子
(4) 加調料
這 4個沖泡飲料的步驟適用於咖啡和茶,在咱們的飲料店裏,根據這 4個步驟製做出來的咖啡和茶,一直順利地提供給絕大部分客人享用。但有一些客人喝咖啡是不加調料(糖和牛奶)的。既然 Beverage 做爲父類,已經規定好了沖泡飲料的 4個步驟,那麼有什麼辦法可讓子類不受這個約束呢?code
鉤子方法( hook )能夠用來解決這個問題,放置鉤子是隔離變化的一種常見手段。咱們在父類中容易變化的地方放置鉤子,鉤子能夠有一個默認的實現,究竟要不要「掛鉤」,這由子類自行決定。鉤子方法的返回結果決定了模板方法後面部分的執行步驟,也就是程序接下來的走向,這樣一來,程序就擁有了變化的可能。在這個例子裏,咱們把掛鉤的名字定爲customerWantsCondiments ,接下來將掛鉤放入 Beverage
類,看看咱們如何獲得一杯不須要糖和牛奶的咖啡,代碼以下:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> 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 = function(){ return window.confirm( '請問須要調料嗎?' ); }; var coffeeWithHook = new CoffeeWithHook(); coffeeWithHook.init(); </script> </body> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <script> var Beverage = function( param ){ var boilWater = function(){ console.log( '把水煮沸' ); }; var brew = param.brew || function(){ throw new Error( '必須傳遞 brew 方法' ); }; var pourInCup = param.pourInCup || function(){ throw new Error( '必須傳遞 pourInCup 方法' ); }; var addCondiments = param.addCondiments || function(){ throw new Error( '必須傳遞 addCondiments 方法' ); }; var F = function(){}; F.prototype.init = function(){ boilWater(); brew(); pourInCup(); addCondiments(); }; return F; }; var Coffee = Beverage({ brew: function(){ console.log( '用沸水沖泡咖啡' ); }, pourInCup: function(){ console.log( '把咖啡倒進杯子' ); }, addCondiments: function(){ console.log( '加糖和牛奶' ); } }); var Tea = Beverage({ brew: function(){ console.log( '用沸水浸泡茶葉' ); }, pourInCup: function(){ console.log( '把茶倒進杯子' ); }, addCondiments: function(){ console.log( '加檸檬' ); } }); var coffee = new Coffee(); coffee.init(); var tea = new Tea(); tea.init(); </script> </body> </html>