模板方法模式是一種只須要使用繼承就能夠實現的設計模式,它一般由兩部分組成,第一部分是抽象父類,第二部分是具體的實現子類。在抽象父類中封裝了子類的算法框架,包括一些公共的方法以及子類中全部方法的執行順序。子類經過繼承父類,也繼承了整個算法結構,以及重寫父類中一些具體的方法。javascript
下面經過咖啡與茶的經典例子來說解模板方法模式的具體實現。
先泡一杯咖啡
泡一杯咖啡一般包括如下步驟:java
經過代碼實現上述步驟爲:程序員
class Coffee {
boilWater () {
console.log('把水煮沸')
}
brewCoffeeGriends () {
console.log('用沸水沖泡咖啡')
}
pourInCup () {
console.log('把咖啡倒進杯子裏')
}
addSugarAndMilk () {
console.log('加糖和牛奶')
}
init () {
this.boilWater()
this.brewCoffeeGriends()
this.pourInCup()
this.addSugarAndMilk()
}
}
const coffee = new Coffee()
coffee.init()
複製代碼
泡一壺茶
而泡一壺茶的步驟跟泡咖啡的步驟相似:web
用代碼描述以下:ajax
class Tea {
boilWater () {
console.log('把水煮沸')
}
steepTeaBag () {
console.log('用沸水浸泡茶葉')
}
pourInCup () {
console.log('把茶水倒進杯子裏')
}
addLemon () {
console.log('加檸檬')
}
init () {
this.boilWater()
this.steepTeaBag()
this.pourInCup()
this.addLemon()
}
}
const tea = new Tea()
tea.init()
複製代碼
分離出共同點
對比泡咖啡和泡茶步驟:算法
泡咖啡 | 泡茶 |
---|---|
把水煮沸 | 把水煮沸 |
用沸水煮咖啡 | 用沸水浸泡茶葉 |
把咖啡倒進杯子裏 | 把茶水倒進杯子裏 |
加糖和牛奶 | 加檸檬 |
分析表格咱們得出泡咖啡和泡茶的不一樣點:設計模式
因而不論是泡咖啡仍是泡茶,咱們能夠用以下代碼描述:架構
class Beverage {
boilWater () {
console.log('把水煮沸')
}
// 空方法,由子類重寫
brew () {}
// 空方法,由子類重寫
pourInCup () {}
// 空方法,由子類重寫
addCondiments () {}
init () {
this.boilWater()
this.brew()
this.pourInCup()
this.addCondiments()
}
}
複製代碼
建立泡咖啡子類:框架
class Coffee extends Beverage {
brew () {
console.log('用沸水煮咖啡')
}
pourInCup () {
console.log('把咖啡倒進杯子裏')
}
addCondiments () {
console.log('加糖和牛奶')
}
}
const coffee = new Coffee()
coffee.init()
複製代碼
這樣咱們經過繼承Beverage類,重寫一些具體方法,就完成了泡咖啡子類的實現。泡茶的Tea實現相似,這裏就再也不具體寫代碼。dom
其實模板方法是一種嚴重依賴抽象類的設計模式,在Java和其它一些靜態類型的語言纔有,而Javascript中是沒有提供抽象的支持的。在Java中有兩種類,一種是抽象類,一種是具體類。具體類是用來實例化的,而抽象類不能被實例化,是用來被繼承的。上面的泡咖啡和泡茶的例子中,咱們抽象了飲料類,裏面包含了一些空方法,若是是抽象類實現的,那這些方法能夠被定義爲抽象方法,繼承該抽象類的子類必須實現那些抽象方法,不然編譯的時候就會報錯。既然JavaSript中沒有抽象類,那該怎麼解決了?下面提供兩種變通方案:
架構師搭建項目
模板方法模式常被架構師用來搭建項目的框架,架構師定義好了框架的骨架,程序員繼承框架的結構以後,負責往裏面填寫內容。好比Java中的HttpServelet技術,一個基於HttpServelet的程序包含7個生命週期,七個生命週期對應7個do方法
它還提供了一個service方法,也就是模板方法,這個方法規定了這些do方法的執行順序,而這些do方法的具體實現則須要HttpServelet的子類來提供。
web構建UI組件
咱們知道構建一個組件的過程通常以下:
分析上述步驟,咱們發現第一步和第四步通常是相同的,中間兩步可能會有變化,因而咱們能夠把上面四步抽象到一個建立組件的父類中,父類提供第一步和第四步的實現,中間兩步由具體的子類重寫。
在模板方法模式中,咱們在父類中封裝了子類的算法框架,這些算法框架適用大多數狀況,可是也會有一些特殊狀況。好比考慮泡咖啡中,可能有些顧客喝咖啡是須要加調料的,可是也有部分顧客不須要加調料,那怎麼去定製這個需求,不讓子類徹底受父類的模板方法約束,鉤子方法就是用來解決這個問題的。鉤子是隔離變化的一種經常使用手段,咱們通常在父類中容易變化的地方放置鉤子,鉤子通常會提供一個默認實現,究竟要不要徹底跟父類的算法框架同樣,子類能夠自行決定。下面實現一個帶customerWantCondiments鉤子的飲料類:
class Beverage {
boilWater () {
console.log('把水煮沸')
}
// 空方法,由子類重寫
brew () {
throw new Error('子類必須重寫brew方法')
}
// 空方法,由子類重寫
pourInCup () {
throw new Error('子類必須重寫pourInCup方法')
}
// 空方法,由子類重寫
addCondiments () {
throw new Error('子類必須重寫addCondiments方法')
}
// 默認須要調料
customerWantCondiments () {
return true
}
init () {
this.boilWater()
this.brew()
this.pourInCup()
if (this.customerWantCondiments()) {
this.addCondiments()
}
}
}
複製代碼
那麼泡咖啡的子類能夠這樣實現:
class CoffeeWithHook extends Beverage {
brew () {
console.log('用沸水煮咖啡')
}
pourInCup () {
console.log('把咖啡倒進杯子裏')
}
addCondiments () {
console.log('加糖和牛奶')
}
customerWantCondiments () {
return window.confirm('請問須要調料嗎?')
}
}
const coffeeWithHook = new CoffeeWithHook()
coffeeWithHook.init()
複製代碼
在模板方法模式中,咱們還學到了一個新的設計原則----著名的好萊塢原則。
在好萊塢中,許多新人演員也找不到工做,他們通常把簡歷遞給演藝公司後就只有回家等電話,有時候等得不耐煩的演員就會打電話給演藝公司詢問狀況怎麼樣,但獲得的回答是:「不要來找我,我會打電話給你」。
在設計中,這種規則就稱爲好萊塢原則。這種原則的思路是,咱們容許底層組件將本身掛鉤到高層組件中,而高層組件決定何時,以何種方式去使用這些底層組件,高層組件對待底層組件的方式就跟演藝公司對待新人演員同樣,都是「別調用咱們,咱們會調用你」。
模板方法模式就是好萊塢原則的一個典型使用場景,當咱們用模板方法編寫一個程序時,就意味着子類放棄了對本身的控制權,而是改成父類去主動調用子類的方法,子類只是提供一些具體的方法的實現。除了模板方法,如下兩種場景也體現了好萊塢原則:
模板方法模式是一種典型的經過封裝變化提升系統擴展性的設計模式。在一個使用了模板方法的模式中,子類的方法和執行順序都是不變的,因此咱們把這部分邏輯抽象到父類的模板方法裏,可變的邏輯部分經過不一樣的子類來實現,經過增長新的子類,給系統添加新的功能,不須要改動抽象父類以及其它子類,符合開放-封閉原則。