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

模版方法模式

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

模式作用:

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

注意事項(xiàng)

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

例子

Coffee of Tea

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

先泡一杯咖啡

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

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();

泡一壺茶

接下來,開始準(zhǔn)備我們的茶,泡茶的步驟跟泡咖啡的步驟相差并不大:
(1) 把水煮沸
(2) 用沸水浸泡茶葉
(3) 把茶水倒進(jìn)杯子
(4) 加檸檬
同樣用一段代碼來實(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();

分類出共同點(diǎn)

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

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

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

使用模版方法:

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();
}

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

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類已經(jīng)完成了,接下來依葫蘆畫瓢,創(chuàng)建我們的Tea類:

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()

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

鉤子方法

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

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

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; //默認(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)問要加調(diào)料嗎?");
}
var coffee=new Coffee();
coffee.init()

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末轴咱,一起剝皮案震驚了整個(gè)濱河市耿眉,隨后出現(xiàn)的幾起案子瘟则,更是在濱河造成了極大的恐慌耸黑,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件零蓉,死亡現(xiàn)場離奇詭異笤受,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)壁公,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門感论,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绅项,“玉大人紊册,你說我怎么就攤上這事】旃ⅲ” “怎么了囊陡?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵,是天一觀的道長掀亥。 經(jīng)常有香客問我撞反,道長,這世上最難降的妖魔是什么搪花? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任遏片,我火速辦了婚禮嘹害,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘吮便。我一直安慰自己笔呀,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布髓需。 她就那樣靜靜地躺著许师,像睡著了一般。 火紅的嫁衣襯著肌膚如雪僚匆。 梳的紋絲不亂的頭發(fā)上微渠,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天,我揣著相機(jī)與錄音咧擂,去河邊找鬼逞盆。 笑死,一個(gè)胖子當(dāng)著我的面吹牛松申,可吹牛的內(nèi)容都是我干的纳击。 我是一名探鬼主播,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼攻臀,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼焕数!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起刨啸,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤堡赔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后设联,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體善已,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年离例,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了换团。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡宫蛆,死狀恐怖艘包,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情耀盗,我是刑警寧澤想虎,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布,位于F島的核電站叛拷,受9級(jí)特大地震影響舌厨,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜忿薇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一裙椭、第九天 我趴在偏房一處隱蔽的房頂上張望躏哩。 院中可真熱鬧,春花似錦揉燃、人聲如沸震庭。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽器联。三九已至,卻和暖如春婿崭,著一層夾襖步出監(jiān)牢的瞬間拨拓,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工氓栈, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留渣磷,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓授瘦,卻偏偏與公主長得像醋界,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子提完,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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