設(shè)計(jì)模式之模版方法模式

模版方法模式

模版方法是一種只需使用繼承就可以實(shí)現(xiàn)的非常簡(jiǎn)單的模式
模版方法模式由兩部分結(jié)構(gòu)組成,第一部分是抽象父類(lèi),第二部分是具體的實(shí)現(xiàn)子類(lèi).通常在抽象父類(lèi)中封裝了子類(lèi)的算法框架,包括實(shí)現(xiàn)一些公共方法以及封裝子類(lèi)中所有方法的執(zhí)行順序.子類(lèi)通過(guò)繼承這個(gè)抽象類(lèi),也繼承了整個(gè)算法結(jié)構(gòu),并且可以選擇重寫(xiě)父類(lèi)的方法

模式作用:

  1. 一次性實(shí)現(xiàn)一個(gè)算法的不變的部分,并將可變的行為留給子類(lèi)來(lái)實(shí)現(xiàn)
  2. 各子類(lèi)中公共的行為應(yīng)被提取出來(lái)并集中到一個(gè)公共父類(lèi)中,避免代碼重復(fù),不同之處分離為新的操作,最后用一個(gè)鉤子的模版方法來(lái)替換這些不同的代碼
  3. 控制子類(lèi)擴(kuò)展,模版方法只在特定點(diǎn)調(diào)用"hook"操作,這樣就允許在這些點(diǎn)進(jìn)行擴(kuò)展

注意事項(xiàng)

  1. 和策略模式不同,模版方法使用繼承來(lái)改變算法的一部分,而策略模式使用委托來(lái)改變整個(gè)算法

例子

Coffee of Tea

咖啡與茶是一個(gè)經(jīng)典的例子,經(jīng)常用來(lái)講解模版方法模式

先泡一杯咖啡

首先,我們先來(lái)泡一杯咖啡,如果沒(méi)有什么太個(gè)性化的的需求,泡咖啡的步驟通常如下:
(1) 把水煮沸
(2) 用沸水煮咖啡
(3) 把咖啡倒進(jìn)杯子
(4) 加糖和牛奶
通過(guò)下面這段代碼,我們就能得到一杯香濃的咖啡:

var Coffee=function(){}
Coffee.prototype.boilWater=function(){
    console.log("把水煮沸");
}
Coffee.prototype.brewCoffeeGriends=function(){
    console.log("用沸水沖泡咖啡");
}
Coffee.prototype.pourInCup=function(){
    console.log("把咖啡倒進(jìn)杯子");
}
Coffee.prototype.addSugarAndMilk=function(){
    console.log("加糖和牛奶");
}
Coffee.prototype.init=function(){
    this.boilWater();
    this.brewCoffeeGriends();
    this.pourInCup();
    this.addSugarAndMilk();
}
var coffee=new Coffee();
coffee.init();

泡一壺茶

接下來(lái),開(kāi)始準(zhǔn)備我們的茶,泡茶的步驟跟泡咖啡的步驟相差并不大:
(1) 把水煮沸
(2) 用沸水浸泡茶葉
(3) 把茶水倒進(jìn)杯子
(4) 加檸檬
同樣用一段代碼來(lái)實(shí)現(xiàn)泡茶的步驟:

var Tea=function(){}
Tea.prototype.boilWater=function(){
    console.log("把水煮沸");
}
Tea.prototype.steepTeaBag=function(){
    console.log("用沸水浸泡茶葉");
}
Tea.prototype.pourInCup=function(){
    console.log("把茶水倒進(jìn)杯子");
}
Tea.prototype.addLemon=function(){
    console.log("加檸檬");
}
Tea.prototype.init=function(){
    this.boilWater();
    this.steepTeaBag();
    this.pourInCup();
    this.addLemon();
}
var tea=new Tea();
tea.init();

分類(lèi)出共同點(diǎn)

現(xiàn)在我們分別泡好了一杯咖啡和一壺茶,經(jīng)過(guò)思考和比較,我們發(fā)現(xiàn)咖啡和茶的沖泡過(guò)程是大同小異的.
我們找到泡咖啡和泡茶主要有以下不同點(diǎn)

  1. 原料不同.一個(gè)是咖啡,一個(gè)是茶,但我們可以把它們抽象為"飲料"
  2. 泡的方式不同.咖啡是沖泡,而茶葉是浸泡,我們可以把它們都抽象為"泡"
  3. 加入的調(diào)料不同.一個(gè)是糖和牛奶,一個(gè)是檸檬,但我們可以把它們都抽象為"調(diào)料"
    經(jīng)過(guò)抽象之后,不管是泡咖啡還是泡茶,我們都能整理為下面四步:
    (1) 把水煮沸
    (2) 用沸水沖泡飲料
    (3) 把飲料倒進(jìn)杯子
    (4) 加調(diào)料

所以,不管是沖泡還是浸泡,我們都能給它一個(gè)新的方法名稱(chēng),比如說(shuō)brew().同理,不管是加糖和牛奶,還是加檸檬,我們都可以稱(chēng)之為addCoundiments()
讓我們忘記最開(kāi)始創(chuàng)建的Coffee和Tea類(lèi).現(xiàn)在可以創(chuàng)建一個(gè)抽象父類(lèi)來(lái)表示泡一杯飲料的整個(gè)過(guò)程.不論是Coffee還是Tea,都被我們用Beverage來(lái)表示,代碼如下:

使用模版方法:

var Beverage=function(){}
Beverage.prototype.boilWater=function(){
    console.log("把水煮沸");
}
Beverage.prototype.brew=function(){
    throw new Error("子類(lèi)必須重寫(xiě)brew方法")
}
Beverage.prototype.pourInCup
=function(){
    throw new Error("子類(lèi)必須重寫(xiě)pourInCup方法")
}
Beverage.prototype.addCondiments=function(){
    throw new Error("子類(lèi)必須重寫(xiě)addCondiments方法")
}
Beverage.prototype.init=function(){
    this.boilWater();
    this.brew();
    this.pourInCup();
    this.addCondiments();
}

創(chuàng)建Coffee子類(lèi)和Tea子類(lèi)

var Coffee=function(){}
Coffee.prototype=new Beverage();
Coffee.prototype.brew=function(){
    console.log("用沸水煮咖啡");
}
Coffee.prototype.pourInCup=function(){
    console.log("把咖啡倒進(jìn)杯子");
}
Coffee.prototype.addCondiments=function(){
    console.log("加糖和牛奶");
}
var coffee=new Coffee();
coffee.init()

現(xiàn)在我們的Coffee類(lèi)已經(jīng)完成了,接下來(lái)依葫蘆畫(huà)瓢,創(chuàng)建我們的Tea類(lèi):

var Tea=function(){}
Tea.prototype=new Beverage();
Tea.prototype.brew=function(){
    console.log("用沸水煮咖啡");
}
Tea.prototype.pourInCup=function(){
    console.log("把咖啡倒進(jìn)杯子");
}
Tea.prototype.addCondiments=function(){
    console.log("加糖和牛奶");
}
var tea=new Tea();
tea.init()

在上面的例子中,到底誰(shuí)才是所謂的模版方法呢?答案是Beverage.prptotype.init
Beverage.prptotype.init被稱(chēng)為模版方法的原因是,該方法封裝了子類(lèi)的算法框架,它作為一個(gè)算法的模版,指導(dǎo)子類(lèi)以何種順序去執(zhí)行哪些方法.在Beverage.prptotype.init方法中,算法內(nèi)的每一個(gè)步驟都清楚地展示在我們眼前.

鉤子方法

通過(guò)模版方法模式,我們?cè)诟割?lèi)中封裝了子類(lèi)的算法框架.這些算法框架在正常狀態(tài)下適用于大多數(shù)子類(lèi)的,但如果有一些特別"個(gè)性"的子類(lèi)呢?比如我們?cè)陲嬃项?lèi)Beverage中封裝了飲料的沖泡順序:
(1) 把水煮沸
(2) 用沸水沖泡飲料
(3) 把飲料倒進(jìn)杯子
(4) 加調(diào)料

這四個(gè)沖泡飲料的步驟適用于咖啡和茶,在我們的飲料店里,根據(jù)這4個(gè)步驟制作出來(lái)的咖啡和茶,一直順利地提供給絕大部分客人享用.但有一些客人喝咖啡是不加調(diào)料的(糖和牛奶)的.既然Bverage作為父類(lèi),已經(jīng)規(guī)定好了沖泡飲料的4個(gè)步驟,那么有什么辦法可以讓子類(lèi)不受這個(gè)約束呢?
鉤子方法(hook)可以用來(lái)解決這個(gè)問(wèn)題,放置鉤子是隔離變化的一種常見(jiàn)手段.我們?cè)诟割?lèi)中容易變化的地方放置鉤子,鉤子可以有一個(gè)默認(rèn)的實(shí)現(xiàn),究竟要不要"掛鉤",這由子類(lèi)自行決定.鉤子方法的返回結(jié)構(gòu)決定了模版方法后面的執(zhí)行步驟,也就是程序接下來(lái)的走向,這樣一來(lái),程序就擁有了變化的可能.
在這個(gè)例子里,我們把掛鉤的名字定位customerWantsCondiments,接下來(lái)將掛鉤放入Beverage類(lèi),看看我們?nèi)绾蔚玫揭槐恍枰呛团D痰目Х?代碼如下:

var Beverage=function(){}
Beverage.prototype.boilWater=function(){
    console.log("把水煮沸");
}
Beverage.prototype.brew=function(){
    throw new Error("子類(lèi)必須重寫(xiě)brew方法")
}
Beverage.prototype.pourInCup=function(){
    throw new Error("子類(lèi)必須重寫(xiě)pourInCup方法")
}
Beverage.prototype.addCondiments=function(){
    throw new Error("子類(lèi)必須重寫(xiě)addCondiments方法")
}
Beverage.prototype.customerWantsCondiments=function(){
    return true; //默認(rèn)需要飲料
}
Beverage.prototype.init=function(){
    this.boilWater();
    this.brew();
    this.pourInCup();
    if(this.customerWantsCondiments()){
        this.addCondiments();
    }
}
var Coffee=function(){}
Coffee.prototype=new Beverage();
Coffee.prototype.brew=function(){
    console.log("用沸水煮咖啡");
}
Coffee.prototype.pourInCup=function(){
    console.log("把咖啡倒進(jìn)杯子");
}
Coffee.prototype.addCondiments=function(){
    console.log("加糖和牛奶");
}
Beverage.prototype.customerWantsCondiments=function(){
    return window.confirm("請(qǐng)問(wèn)要加調(diào)料嗎?");
}
var coffee=new Coffee();
coffee.init()

在JavaScript中,我們很多時(shí)候不需要依樣畫(huà)瓢地實(shí)現(xiàn)一個(gè)模版方法模式,高階函數(shù)是更好的選擇.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評(píng)論 6 538
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,067評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,467評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀(guān)的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,468評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,184評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,582評(píng)論 1 325
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,794評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,343評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,096評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,291評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,513評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,941評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,190評(píng)論 1 291
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,026評(píng)論 3 396
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,253評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容