編寫可維護(hù)性的JavaScript 之設(shè)計(jì)模式 (二)

模板方法模式

模板方法主要由兩部分構(gòu)成, 第一部分是抽象父類,第二部分是具體實(shí)現(xiàn)的子類,通常我們?cè)诔橄蟾割愔蟹庋b子類的算法框架,子類通過(guò)繼承這個(gè)抽象類,也繼承了整個(gè)算法結(jié)構(gòu)
要講模板方法模式 首先來(lái)看一個(gè)著名的例子 Coffee or Tea,這個(gè)例子的原型來(lái)自于《Head First設(shè)計(jì)模式》
泡一杯咖啡的步驟

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

泡一壺茶的步驟

  1. 把水煮沸
  2. 用沸水浸泡茶葉
  3. 把茶葉倒進(jìn)杯子
  4. 加檸檬

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

對(duì)比這兩件事情,我們發(fā)現(xiàn)他們的步驟是一樣的。所以我們可以將這個(gè)步驟的算法封裝到抽象父類中,然后在子類中來(lái)做當(dāng)前步驟的具體實(shí)現(xiàn)

var Beverage = function(){};
Beverage.prototype.boilWater = function(){
     console.log( '把水煮沸' );
};
//這里保存空方法, 由子類來(lái)重寫
Beverage.prototype.brew = function(){};
Beverage.prototype.pourInCup = function(){}; 
Beverage.prototype.addCondiments = function(){};
Beverage.prototype.init = function(){
     this.boilWater();
     this.brew();
     this.pourInCup(); 
     this.addCondiments();
};

創(chuàng)建Coffee子類

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

這段代碼做了個(gè)什么事情呢, Coffee繼承了Beverage ,并且在自己的原型鏈上復(fù)寫了Beverage.init()執(zhí)行的方法,也就是泡一杯咖啡每一個(gè)步驟執(zhí)行的具體方法。這樣在執(zhí)行初始化的時(shí)候各個(gè)步驟操作邏輯實(shí)現(xiàn)是由子類實(shí)現(xiàn)的,而邏輯的控制是由父類來(lái)控制的
我們用相同的方式創(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.prototype.init,這個(gè)方法中封裝了子類算法框架,作為一個(gè)算法模板被子類調(diào)用

抽象類

模板方法模式是一種嚴(yán)重依賴于抽象類的設(shè)計(jì)模式,在Java中,類分為兩種,一種是抽象類,一種是具體類。具體類可以被實(shí)例化,抽象類不能被實(shí)例化。在前面的例子中,比如茶,你可以說(shuō)給我來(lái)個(gè)新的茶,是指的一類具體的事物,但是你不能說(shuō)給我來(lái)個(gè)新的飲料,因?yàn)椴韬涂Х榷际秋嬃?,所以抽象類不能被?shí)例化。由于抽象類不能被實(shí)例化,如果有人編寫了一個(gè)抽象類,那么這個(gè)抽象類一定是用來(lái)被某些 具體類繼承的

在靜態(tài)類型語(yǔ)言中, 編譯器對(duì)類型的檢查總是一個(gè)繞不過(guò)的話題與困擾。雖然類型檢查可以提高程序的安全性,但繁 瑣而嚴(yán)格的類型檢查也時(shí)常會(huì)讓程序員覺(jué)得麻煩。把對(duì)象的真正類型隱藏在抽象類或者接口之 后,這些對(duì)象才可以被互相替換使用。這可以讓我們的 Java 程序盡量遵守依賴倒置原則。除了用于向上轉(zhuǎn)型,抽象類也可以表示一種契約。繼承了這個(gè)抽象類的所有子類都將擁有跟 抽象類一致的接口方法,抽象類的主要作用就是為它的子類定義這些公共接口。如果我們?cè)谧宇?中刪掉了這些方法中的某一個(gè),那么將不能通過(guò)編譯器的檢查,這在某些場(chǎng)景下是非常有用的。

JavaScript 沒(méi)有抽象類的缺點(diǎn)和解決方案

當(dāng)我們?cè)?JavaScript 中使用原型繼承來(lái)模擬傳統(tǒng)的類式繼承時(shí),并沒(méi)有編譯器幫 助我們進(jìn)行任何形式的檢查,我們也沒(méi)有辦法保證子類會(huì)重寫父類中的“抽象方法”。

如果我們的 Coffee 類或者 Tea 類忘記實(shí)現(xiàn)這 4個(gè)方法中的一個(gè)呢?拿 brew 方法舉例,如果 我們忘記編寫 Coffee.prototype.brew 方法,那么當(dāng)請(qǐng)求 coffee 對(duì)象的 brew 時(shí),請(qǐng)求會(huì)順著原型 鏈找到 Beverage“父類”對(duì)應(yīng)的 Beverage.prototype.brew 方法,而 Beverage.prototype.brew 方法 到目前為止是一個(gè)空方法,這顯然是不能符合我們需要的

在 Java 中編譯器會(huì)保證子類會(huì)重寫父類中的抽象方法,但在 JavaScript 中卻沒(méi)有進(jìn)行這些檢 查工作。我們?cè)诰帉懘a的時(shí)候得不到任何形式的警告,完全寄托于程序員的記憶力和自覺(jué)性是 很危險(xiǎn)的,特別是當(dāng)我們使用模板方法模式這種完全依賴?yán)^承而實(shí)現(xiàn)的設(shè)計(jì)模式時(shí)

提供兩種變通的解決方案

  • 第 1 種方案是用鴨子類型來(lái)模擬接口檢查,以便確保子類中確實(shí)重寫了父類的方法。但模擬接口檢查會(huì)帶來(lái)不必要的復(fù)雜性,而且要求程序員主動(dòng)進(jìn)行這些接口檢查,這就要求
    我們?cè)跇I(yè)務(wù)代碼中添加一些跟業(yè)務(wù)邏輯無(wú)關(guān)的代碼。
  • 第 2 種方案是讓 Beverage.prototype.brew 等方法直接拋出一個(gè)異常,如果因?yàn)榇中耐浘帉?Coffee.prototype.brew 方法,那么至少我們會(huì)在程序運(yùn)行時(shí)得到一個(gè)錯(cuò)誤.
Beverage.prototype.brew = function(){
    throw new Error( '子類必須重寫 brew 方法' );
};
Beverage.prototype.pourInCup = function(){
    throw new Error( '子類必須重寫 pourInCup 方法' );
};
Beverage.prototype.addCondiments = function(){
    throw new Error( '子類必須重寫 addCondiments 方法');
}

第 2 種解決方案的優(yōu)點(diǎn)是實(shí)現(xiàn)簡(jiǎn)單,付出的額外代價(jià)很少;缺點(diǎn)是我們得到錯(cuò)誤信息的時(shí)間點(diǎn)太靠后。

本文部分摘錄自 曾探《javascript 設(shè)計(jì)模式與開(kāi)發(fā)實(shí)踐》這本書寫得相當(dāng)出彩 強(qiáng)烈推薦
我的博客 https://yangfan0095.github.io

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

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

  • 單例模式 適用場(chǎng)景:可能會(huì)在場(chǎng)景中使用到對(duì)象,但只有一個(gè)實(shí)例,加載時(shí)并不主動(dòng)創(chuàng)建,需要時(shí)才創(chuàng)建 最常見(jiàn)的單例模式,...
    Obeing閱讀 2,088評(píng)論 1 10
  • 工廠模式類似于現(xiàn)實(shí)生活中的工廠可以產(chǎn)生大量相似的商品,去做同樣的事情,實(shí)現(xiàn)同樣的效果;這時(shí)候需要使用工廠模式。簡(jiǎn)單...
    舟漁行舟閱讀 7,804評(píng)論 2 17
  • 在 JavaScript 開(kāi)發(fā)中用到繼承的場(chǎng)景其實(shí)并不是很多,很多時(shí)候我們都喜歡用 mix-in 的方式給對(duì)象擴(kuò)展...
    風(fēng)銘閱讀 393評(píng)論 0 0
  • 設(shè)計(jì)模式基本原則 開(kāi)放-封閉原則(OCP),是說(shuō)軟件實(shí)體(類、模塊、函數(shù)等等)應(yīng)該可以拓展,但是不可修改。開(kāi)-閉原...
    西山薄涼閱讀 3,840評(píng)論 3 14
  • 面向?qū)ο缶幊?1.創(chuàng)建,使用函數(shù) var CheckObject = {checkName : function(...
    依米花1993閱讀 398評(píng)論 0 0