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

模版方法模式

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

模式作用:

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

注意事項

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

例子

Coffee of Tea

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

先泡一杯咖啡

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

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

泡一壺茶

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

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

分類出共同點

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

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

所以,不管是沖泡還是浸泡,我們都能給它一個新的方法名稱,比如說brew().同理,不管是加糖和牛奶,還是加檸檬,我們都可以稱之為addCoundiments()
讓我們忘記最開始創(chuàng)建的Coffee和Tea類.現(xiàn)在可以創(chuàng)建一個抽象父類來表示泡一杯飲料的整個過程.不論是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("把咖啡倒進杯子");
}
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("把咖啡倒進杯子");
}
Tea.prototype.addCondiments=function(){
    console.log("加糖和牛奶");
}
var tea=new Tea();
tea.init()

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

鉤子方法

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

這四個沖泡飲料的步驟適用于咖啡和茶,在我們的飲料店里,根據(jù)這4個步驟制作出來的咖啡和茶,一直順利地提供給絕大部分客人享用.但有一些客人喝咖啡是不加調(diào)料的(糖和牛奶)的.既然Bverage作為父類,已經(jīng)規(guī)定好了沖泡飲料的4個步驟,那么有什么辦法可以讓子類不受這個約束呢?
鉤子方法(hook)可以用來解決這個問題,放置鉤子是隔離變化的一種常見手段.我們在父類中容易變化的地方放置鉤子,鉤子可以有一個默認的實現(xiàn),究竟要不要"掛鉤",這由子類自行決定.鉤子方法的返回結(jié)構(gòu)決定了模版方法后面的執(zhí)行步驟,也就是程序接下來的走向,這樣一來,程序就擁有了變化的可能.
在這個例子里,我們把掛鉤的名字定位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; //默認需要飲料
}
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("把咖啡倒進杯子");
}
Coffee.prototype.addCondiments=function(){
    console.log("加糖和牛奶");
}
Beverage.prototype.customerWantsCondiments=function(){
    return window.confirm("請問要加調(diào)料嗎?");
}
var coffee=new Coffee();
coffee.init()

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末税灌,一起剝皮案震驚了整個濱河市宽闲,隨后出現(xiàn)的幾起案子歧蒋,更是在濱河造成了極大的恐慌化漆,老刑警劉巖村斟,帶你破解...
    沈念sama閱讀 211,042評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異稠歉,居然都是意外死亡煤杀,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評論 2 384
  • 文/潘曉璐 我一進店門谒出,熙熙樓的掌柜王于貴愁眉苦臉地迎上來羽杰,“玉大人渡紫,你說我怎么就攤上這事】既” “怎么了?”我有些...
    開封第一講書人閱讀 156,674評論 0 345
  • 文/不壞的土叔 我叫張陵莉测,是天一觀的道長颜骤。 經(jīng)常有香客問我,道長捣卤,這世上最難降的妖魔是什么忍抽? 我笑而不...
    開封第一講書人閱讀 56,340評論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮董朝,結(jié)果婚禮上鸠项,老公的妹妹穿的比我還像新娘。我一直安慰自己子姜,他們只是感情好祟绊,可當我...
    茶點故事閱讀 65,404評論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著哥捕,像睡著了一般牧抽。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上遥赚,一...
    開封第一講書人閱讀 49,749評論 1 289
  • 那天扬舒,我揣著相機與錄音,去河邊找鬼凫佛。 笑死讲坎,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的愧薛。 我是一名探鬼主播晨炕,決...
    沈念sama閱讀 38,902評論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼厚满!你這毒婦竟也來了府瞄?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,662評論 0 266
  • 序言:老撾萬榮一對情侶失蹤碘箍,失蹤者是張志新(化名)和其女友劉穎遵馆,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體丰榴,經(jīng)...
    沈念sama閱讀 44,110評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡货邓,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,451評論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了四濒。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片换况。...
    茶點故事閱讀 38,577評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡职辨,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出戈二,到底是詐尸還是另有隱情舒裤,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評論 4 328
  • 正文 年R本政府宣布觉吭,位于F島的核電站腾供,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鲜滩。R本人自食惡果不足惜伴鳖,卻給世界環(huán)境...
    茶點故事閱讀 39,848評論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望徙硅。 院中可真熱鬧榜聂,春花似錦、人聲如沸嗓蘑。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽脐往。三九已至休吠,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間业簿,已是汗流浹背瘤礁。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留梅尤,地道東北人柜思。 一個月前我還...
    沈念sama閱讀 46,271評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像巷燥,于是被迫代替她去往敵國和親赡盘。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,452評論 2 348

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