模式到今天已經(jīng)20年歷史了, 尤其是GoF模式, 更廣泛被大家熟知, 這些模式就像武俠小說(shuō)里的招式, 什么時(shí)候該使用什么招式, 師傅們只能泛泛而談, 正確的判斷必須由我自己的做出. 以后會(huì)介紹些自己的淺薄經(jīng)驗(yàn), 并和大家討論, 只是不知道如何寫(xiě)起; 無(wú)論你是存在問(wèn)題, 還是已經(jīng)有了自己的答案, 都可以給我留言; 對(duì)于前者我不見(jiàn)得會(huì)馬上回復(fù), 但是對(duì)我如何去寫(xiě)有很大啟發(fā). 在這里先整理一些有疑問(wèn)的使用方式, 作為未來(lái)的素材.
職責(zé)鏈?zhǔn)褂们榫耙? 原帖 在表達(dá)我的想法之前, 我們先優(yōu)化一下作者原來(lái)的代碼中如下部分, 雖然這些優(yōu)化中有一些對(duì)于真正的職責(zé)鏈模式是錯(cuò)誤的, 而在這情況下使用鏈表是存在很大疑問(wèn)的, 但是我們?nèi)绻痪心嘤诼氊?zé)鏈稱(chēng)號(hào), 也不考慮使用鏈表的合理性, 則這些優(yōu)化可以當(dāng)作一次重構(gòu)的展示:
對(duì)象一: if (變量 == "信息一") 某操作 else if (存在下一個(gè)對(duì)象) 下一對(duì)象.執(zhí)行() else 默認(rèn)操作 對(duì)象二: if (變量 == "信息二") 某操作 else if (存在下一個(gè)對(duì)象) 下一對(duì)象.執(zhí)行() else 默認(rèn)操作 ... ...
這是什么? 這就是重復(fù), 這種重復(fù)如果混進(jìn)代碼里往往沒(méi)有那么明顯; 先把最明顯的部分搞掉吧:
public class BasicHttpBindingConstraint:BindingConstraint { public override bool Constraint(ServiceEndpoint endpoint) { if (endpoint.Binding.Name == "BasicHttpBinding") { //原操作 } return base.Constraint(endpoint); } } 判斷是否有下一個(gè)等等寫(xiě)入基類(lèi), 還是原來(lái)那些代碼. 這樣重構(gòu)的好處除了消除了代碼重復(fù), 原文中兩個(gè)protected的字段, 也不用再向?qū)崿F(xiàn)類(lèi)暴露, 做到了更好的信息隱藏. 但是真的沒(méi)有重復(fù)了嗎? 看看這句: if (endpoint.Binding.Name == "BasicHttpBinding"), 其中變化的只有引號(hào)內(nèi)部的部分, 其余的還是重復(fù). 同時(shí), 每一個(gè)實(shí)現(xiàn)類(lèi), 雖然不用寫(xiě)else了但是base.Constraint(endpoint)卻得Copy&Paste一遍.
接下來(lái)我們這么干一下基類(lèi):
public abstract class BindingConstraint//:IEndpointConstraint { #region protected fields //只要是field, 最好連protected也不要. #endregion
private BindingConstraint m_bindingConstraint; //與原來(lái)相比, private做到了更好的信息隱藏 private bool m_hasNextConstraint = false;
#region public methods
public void AddConstraint(BindingConstraint constraint) { m_bindingConstraint = constraint; m_hasNextConstraint = true; }
public bool Constraint(ServiceEndpoint endpoint) //注意, 不再abstract了 { if (BindingConstraintName == endpoint.Binding.Name) { return DoConstraint(endpoint);//注意, 這么優(yōu)化是不對(duì)的, 見(jiàn)后面關(guān)于職責(zé)鏈的討論 } else { if (m_hasNextConstraint) { return m_bindingConstraint.Constraint(endpoint); } else { return false; } } }
#endregion
#region public abstract methods //新增了兩個(gè) protected abstract bool DoConstraint(ServiceEndpoint endpoint); protected abstract String BindingConstraintName { get; }
#endregion
} 根據(jù)傳說(shuō)中的單一職責(zé), 作為實(shí)現(xiàn)的子類(lèi), 無(wú)需再負(fù)責(zé)鏈表的相關(guān)工作, 只要管自己干嘛, 并提供自己負(fù)責(zé)事情的代號(hào), 就可以了. 但是我要說(shuō)明的是, GoF的代碼例子, 是我和當(dāng)前例子的作者的那個(gè)中間值: 第一次優(yōu)化后那個(gè)結(jié)果; 為什么呢?
第一次優(yōu)化后, base.Constraint(endpoint)這個(gè), 還可以不執(zhí)行, 這樣我們就有了一個(gè)機(jī)會(huì), 定義默認(rèn)操作, 甚至將職責(zé)鏈原本的鏈條改變, 引導(dǎo)到另外一個(gè)職責(zé)鏈上去. 請(qǐng)大家注意這個(gè)事實(shí): 使用base.Constraint(endpoint)的方法, 增加了重復(fù), 但換到了靈活性; 也就是說(shuō), 只要是損失的, 必須換來(lái)其它的東西, 否則回家咱老婆也不干啊不是.
只是針對(duì)這個(gè)例子, 把去優(yōu)化它兩次沒(méi)啥問(wèn)題, 不過(guò)仍然可以看作吃飽了撐的沒(méi)事干, 唯一的作用就是順便就問(wèn)這么一個(gè)事情:
信息隱藏完美, 沒(méi)有重復(fù), 使用優(yōu)雅, 對(duì)擴(kuò)展開(kāi)放, 一切符合教科書(shū)上各種原則的代碼. 就一定是正確的代碼嗎?
該文作者使用鏈表作設(shè)計(jì), 無(wú)可厚非, 畢竟, 他達(dá)到了他想要的目的; 但是其實(shí)這種用法, 存在著一個(gè)的問(wèn)題: 鏈表上每一個(gè)對(duì)象, 都要保存著上一個(gè)對(duì)象的引用, 從接到請(qǐng)求, 到有一個(gè)對(duì)象響應(yīng), 中間有多少個(gè)對(duì)象, 就有多少個(gè)廢開(kāi)銷(xiāo). 有開(kāi)銷(xiāo)沒(méi)有問(wèn)題, 問(wèn)題是開(kāi)銷(xiāo)換來(lái)的是什么? 該文作者總結(jié)到:
引用(錯(cuò)誤!見(jiàn)后)使用職責(zé)鏈模式必須恰到好處,否則會(huì)成為模式濫用的反面教材。根據(jù)我對(duì)職責(zé)鏈模式的理解,可以認(rèn)定只要同時(shí)符合下列三個(gè)條件,就可以引入職責(zé)鏈模式: 1、當(dāng)一個(gè)方法的傳入?yún)?shù)將成為分支語(yǔ)句的判斷條件時(shí); 2、當(dāng)每一個(gè)分支的職責(zé)相對(duì)獨(dú)立,且邏輯較為復(fù)雜時(shí); 3、當(dāng)分支條件存在擴(kuò)展的可能時(shí)。 我的看法是, 基本上打到靶子外去了. 那些可能產(chǎn)生的開(kāi)銷(xiāo), 其實(shí)是白白犧牲了還被扣了個(gè)"廢"的帽子; 勤奮工作的CPU, 被我一個(gè)初學(xué)者硬生生的釘上了"貪污和浪費(fèi), 是極大的犯罪"的歷史的恥辱柱. 我們這次付出, 不像上面GoF的老大們選擇的不優(yōu)化, 反而像談戀愛(ài), 大把的白花花的銀子花出去, 最后只等到一張好人卡~
比如用Strategy模式實(shí)現(xiàn), 照樣可以得到這些好處. 比如該文作者的例子:
1. 創(chuàng)建一個(gè)類(lèi)作為執(zhí)行者, 執(zhí)行者中持有一個(gè)保寸字符串和IBindingConstraint的字典(比如Dictionary, HashTable); 2. 這個(gè)字典的初始化, 既可以用原來(lái)Add的方式, 也可以通過(guò)一個(gè)配置文件初始化; 3. 實(shí)現(xiàn)一個(gè)Execute, 根據(jù)endpoint.Binding.Name從字典取出IBindingConstraint, 并執(zhí)行Constraint; 4. 使用時(shí)調(diào)用: 推薦者.Execute(endpoint).
很顯然, 這樣的做法, 不但有更小的開(kāi)銷(xiāo), 也有更少的代碼, 同時(shí)擁有更合理的結(jié)構(gòu). 在這里我要隆重的提醒大家注意使用字典, 這種方式雖然不入GoF 23種模式大雅之堂, 卻是n多大牛/大嘴推薦的萬(wàn)靈丹. 對(duì)字典的使用也有一個(gè)廣泛學(xué)名, 叫做"表模式"(還是什么來(lái)著? 記性不好...). 當(dāng)然就針對(duì)當(dāng)前的討論所針對(duì)的問(wèn)題來(lái)說(shuō), 我們也可以把它理解為策略模式的一個(gè)應(yīng)用.
為什么說(shuō)這種方式更合理呢? 讓我們來(lái)看一下原文作者所說(shuō)的三條, 其模型到底是什么樣的:
信息1 -> 過(guò)程或?qū)ο笠?BR>信息2 -> 過(guò)程或?qū)ο蠖?BR>....
SELECT 對(duì)象 FROM 某處 WHERE 信息 = @變量, 查表, 赤裸裸的查表! 職責(zé)鏈呢? 作為一個(gè)設(shè)計(jì)模式, 僅僅是針對(duì)查表然后執(zhí)行算法, 那策略模式早就對(duì)這個(gè)搶飯碗的提反對(duì)意見(jiàn)了. 事實(shí)上職責(zé)鏈到底是干嘛地的呢? 搞清楚這個(gè)問(wèn)題, 就可以判斷真正的職責(zé)鏈在這里是不是合適, 或者原文作者使用鏈表的方式, 是否是職責(zé)鏈所覆蓋的范疇了.
DP955.1 CHAIN OF RESPONSIBILITY 1. 意圖 使多個(gè)對(duì)象都有機(jī)會(huì)處理請(qǐng)求...... 2. 動(dòng)機(jī) 考慮一個(gè)圖形界面中上下文有關(guān)的幫助機(jī)制.....如果對(duì)于那一部分界面沒(méi)有特定的幫助信息, 那么...顯示一個(gè)關(guān)于當(dāng)前上下文的較一般的幫助信息, 比如說(shuō)..... 因此很自然地, 應(yīng)根據(jù)普遍性即從最特殊的到最普通的順序來(lái)組織.. 3. 適用性
有多個(gè)對(duì)象可以處理一個(gè)請(qǐng)求, 哪個(gè)對(duì)象處理一個(gè)請(qǐng)求運(yùn)行時(shí)刻自動(dòng)確定. ... ...
找啊找啊找朋友, 找到一個(gè)女朋友~ 沒(méi)錯(cuò), 職責(zé)鏈說(shuō)的就是這個(gè)事: 你認(rèn)識(shí)一個(gè)特別出色的女人, 你跟她說(shuō), 做我馬子吧, 如果她同意了, 自然好; 如果她說(shuō), 你就像我哥哥, 于是你說(shuō)了, 既然我是你哥哥, 你得幫哥哥介紹一個(gè)吧? 她為了盡快擺脫你, 把一個(gè)比她差點(diǎn)的朋友介紹給了你, 然后你說(shuō), 做我馬子吧... 如此下去, 直到有一個(gè)人接受你為止.
有一個(gè)問(wèn)題我們要想到: 假設(shè)那個(gè)特別出色的女人是A, 她那天正好想到的朋友是B, 你就去給B花錢(qián)了; 如果想到的是C, 那你就是給C花錢(qián); 同時(shí), B如果不同意, 也許會(huì)給你C的電話(huà), 也許會(huì)給你D或者E的電話(huà). 而B(niǎo)CDE都有可能接受你. 也就是說(shuō), 誰(shuí)把你接收了是不一定的, 但總有一個(gè)能接收你, 當(dāng)然這里假設(shè)你是個(gè)女人就行, 換個(gè)說(shuō)法, 你是無(wú)要求的, 響應(yīng)者是不固定的. 將Gof的例子形象化, 想像所有的東西都是卡片, 疊在一起 如下:
-- ------ -------------- --------------------
從正上方看, 是一個(gè)窗口平面, 最頂層的那個(gè)區(qū)域是某種Widget1, 下面一種是 Widget2, 再下面一種是Widget3, 最底下是4, 記住, 全都是不同的class, 繼承自一個(gè)Handler. 當(dāng)我們用小問(wèn)號(hào)點(diǎn)擊Widget1所在區(qū)域的時(shí)候, 如果Widget1含有幫助信息, 就顯示W(wǎng)idget1的, 如果沒(méi)有, 就顯示W(wǎng)idget2的以此類(lèi)推. 當(dāng)然, 真實(shí)的例子不會(huì)這么擺, 只會(huì)比這個(gè)更復(fù)雜, 從最底層的對(duì)象開(kāi)始, 往上基本是一個(gè)樹(shù)狀的樣子. 我們可以認(rèn)為上面的圖, 是這棵樹(shù)某一個(gè)分支, 正好就這么簡(jiǎn)單.
我們換一個(gè)放法: Widget1下面是Widget7, Widget3, Widget9(按順序), 也就是說(shuō), 隊(duì)伍根本不同了; 尋找的過(guò)程仍然相似, 只是最終顯示的幫助信息有可能不一樣. 為什么呢? 我們考慮一個(gè)控件: Label. Label本身一般沒(méi)有幫助, 我們把它放入Widget2里, 它如果被點(diǎn)擊了, 其效果和點(diǎn)擊Widget2應(yīng)該是一樣的; 換成Widget7如果Widget7有幫助, 就應(yīng)該顯示W(wǎng)idget7的幫助. 但如果正好Widget7也沒(méi)有幫助呢? 就顯示W(wǎng)idget3的唄, 因?yàn)長(zhǎng)abel既然是Widget7的一部分, 顯然也是Widget3的一部分; Widget7自己被點(diǎn)擊, 顯示的也是Widget3的幫助.
我們需要看到的是: Widget7和Widget3, 顯示幫助的方式可能完全不同, 這原因并非因?yàn)閃idget7對(duì)應(yīng)了一個(gè)既定的傳入信息而Widget3對(duì)應(yīng)了另外一個(gè), 而是因?yàn)閃idget的畫(huà)圖方式可能和Widget3不同; 即這種不同不是對(duì)應(yīng)于外部對(duì)象, 而是職責(zé)鏈上對(duì)象本身的特性所確定的. 這就決定了, 在運(yùn)行時(shí), 對(duì)于某一信息, 我們改變了這一信息的處理方式. 沒(méi)人處理的方式一般也不是返回false, 而是交給最終的一個(gè)DefaultHandler, 做默認(rèn)操作; 這表面看跟直接返回沒(méi)啥區(qū)別, 每次還必須將DefaultHandler接到職責(zé)鏈尾端, 但實(shí)質(zhì)上保證了處理方式的統(tǒng)一.
另外, GoF的例子中, 一個(gè)對(duì)象是否響應(yīng)職責(zé)鏈, 也是不固定的, 這話(huà)看著和上面重復(fù), 其實(shí)這是另一個(gè)不固定: 任何一個(gè)從基類(lèi)繼承下來(lái)的對(duì)象, 比如上面這些Widget, 如果它被設(shè)置了幫助, 當(dāng)它被點(diǎn)擊就會(huì)響應(yīng), 如果沒(méi)有設(shè)定幫助(默認(rèn)值), 就應(yīng)該由它傳遞給更下層的一個(gè). 這個(gè)情況下Widget7響應(yīng)這個(gè)請(qǐng)求, 另一個(gè)情況也許就不響應(yīng)了, 這只和是否給Widget7設(shè)置了幫助文本有關(guān). 即: 判斷是不是響應(yīng)的條件, 并非傳入對(duì)象持有的信息(事實(shí)上GoF的例子根本沒(méi)有傳入對(duì)象), 而是由職責(zé)鏈上的對(duì)象自己持有的信息來(lái)確定.在GoF的說(shuō)法里, 非常關(guān)鍵的一個(gè)詞, 就是上下文相關(guān), 這才是職責(zé)鏈的精髓所在, 而傳遞這一形式, 僅僅是具體做法罷了.
最后, 考慮這個(gè)窗口平面的例子, 我們用小問(wèn)號(hào)點(diǎn)擊的Widget, 不一定是處于鏈表頭的對(duì)象, 我們的點(diǎn)擊可以落在鏈表中任何一層上任何一個(gè)對(duì)象所對(duì)應(yīng)的范圍內(nèi), 所以鏈表中每一個(gè)對(duì)象都肯能而且可以成為入口. 按照原文例子的用法, 所有的請(qǐng)求都只能從鏈表頭也就是最上層進(jìn)去, 這和DP95上所描述的意圖和結(jié)構(gòu)完全不符, 在GoF描述的職責(zé)鏈功能和用法中, 每一個(gè)具體子類(lèi)都可能響應(yīng)請(qǐng)求, 這是由輸入動(dòng)態(tài)決定的, 如果只從頭部進(jìn)去, 只不準(zhǔn)哪個(gè)不該負(fù)責(zé)的對(duì)象就進(jìn)行了不正確響應(yīng): 比如點(diǎn)擊的Widget3, 請(qǐng)求卻讓W(xué)idget1給截獲了.
不同的子類(lèi), 只是行為不同, 而不代表該子類(lèi)從根本上只可以處理它對(duì)應(yīng)的信息, 不能處理其它信息, 即不存在(信息1 --> 行為1)這樣的對(duì)應(yīng)關(guān)系; 每一個(gè)行為都應(yīng)該可以處理任意一種信息, 子類(lèi)改變的就只是處理方式, 而到底哪一個(gè)用什么方式處理, 則是動(dòng)態(tài)的根據(jù)狀態(tài)決定的. 正是因?yàn)槲覀儾恢滥骋粚?duì)象, 是否會(huì)處理這個(gè)信息, 所以我們就無(wú)法應(yīng)用表模式, 這樣就需要職責(zé)鏈模式來(lái)解決.這才是23種而不是22種設(shè)計(jì)模式的原因.
比如這樣的形式:
XxxBindingConstraint x = new XxxBindingConstraint(); x.SetParent(previousBindingConstraint); x.AddNames({"Basic", "WS", "xxx".});
很顯然, 如果這樣修改原文的例子, 雖然每個(gè)對(duì)象都能響應(yīng)請(qǐng)求了, 卻不能正確執(zhí)行: 但因?yàn)槠湓O(shè)計(jì)目的緣故, 比如Basic對(duì)應(yīng)的實(shí)現(xiàn)只并不負(fù)責(zé)處理WS, 只能等著拋出異常, 根本不就不符合這個(gè)意義:
DP95第五章開(kāi)頭 Chain of Responsibility(5.1)....,根據(jù)運(yùn)行時(shí)刻情況任一候選者都可以響應(yīng)相應(yīng)的請(qǐng)求.
關(guān)于從鏈條中間開(kāi)始響應(yīng)請(qǐng)求倒是不需要改, 只是這一行為根本無(wú)法做出: 因?yàn)檫@個(gè)設(shè)計(jì)從根本上講只能夠從頭部遍歷到那個(gè)屬于他的真命天女, 而并非由運(yùn)行時(shí)輸入所指定的那個(gè)開(kāi)始找一個(gè)最合適的對(duì)象. 所以我們可以說(shuō), 這個(gè)例子只是徒具鏈表的形式, 卻根本沒(méi)有考慮鏈表更適合哪些情況, 在那些情況下如何發(fā)揮正面作用的; 無(wú)論這一設(shè)計(jì)實(shí)現(xiàn)的多么完美, 它相比其它選擇, 更多的是擔(dān)負(fù)了不該有的負(fù)面效果. 拋開(kāi)是不是明白或誤解了什么設(shè)計(jì)模式這樣毫無(wú)重要性的事情, 這在我看來(lái)就像在過(guò)去經(jīng)常提到的, 是缺乏對(duì)事物的正確分析的緣故. 這一前提, 才是做出良好設(shè)計(jì)的唯一前提, 哪怕你都不知道設(shè)計(jì)模式是啥, 甚至連面向?qū)ο髱讉(gè)字怎么寫(xiě)都不知道.
多說(shuō)一句, 對(duì)于GoF的例子, 其實(shí)也存在著很多粗陋之處(過(guò)于簡(jiǎn)單就不算了); 比如是不是HasHelp, 由基類(lèi)提供一個(gè)方法判斷, 但是如果HasHelp()傳回false, 由咱們實(shí)現(xiàn)的子類(lèi)來(lái)轉(zhuǎn)發(fā). 這個(gè)模型產(chǎn)生了一個(gè)不一致性: 如果基類(lèi)的HasHelp()認(rèn)為沒(méi)有Help, 但其實(shí)你根本沒(méi)有用基類(lèi)這個(gè)邏輯, 按照子類(lèi)的邏輯是有Help的所以你就直接響應(yīng)了呢? 于是為了消除這個(gè)不一致性, 我們實(shí)現(xiàn)子類(lèi)時(shí)就不得不給基類(lèi)提供信息, 這就可能會(huì)增加不必要的繁文縟節(jié). 只是這些不完美, 可以理解為示例的不嚴(yán)謹(jǐn), 總體來(lái)說(shuō)是沒(méi)有任何大的漏洞的.
再看看原帖作者的例子: 每一個(gè)傳入的對(duì)象, 有一個(gè)明確的接收者; 鏈上除這個(gè)明確接收者之外的其它對(duì)象, 絕不會(huì)處理請(qǐng)求, 而這個(gè)明確的接收者, 是否處理某請(qǐng)求這一要素是固定的. 也就是說(shuō), 鏈上如果沒(méi)有這個(gè)明確的接收者, 該請(qǐng)求不會(huì)被處理; 而不是因?yàn)殒溕系慕邮照邆冊(cè)谶\(yùn)行時(shí)刻根據(jù)具體情況判斷是否響應(yīng)這個(gè)請(qǐng)求, 從而改變響應(yīng)請(qǐng)求所執(zhí)行的行為. 這樣的用法, 半點(diǎn)職責(zé)鏈的獨(dú)有好處也得不到, 只能說(shuō)形似職責(zé)鏈, 實(shí)則表模式, 而且由于是鏈表, 就變成了負(fù)擔(dān)職責(zé)鏈開(kāi)銷(xiāo)的策略模式.
怪怪設(shè)計(jì)模式第一反律: 可以找出對(duì)應(yīng)關(guān)系的, 不要使用鏈表, 如果硬以傳遞的方式成鏈, 也不能叫做職責(zé)鏈, 只能叫做對(duì)象鏈表. 職責(zé)鏈?zhǔn)且桓溕厦恳画h(huán)節(jié)皆可響應(yīng), 但不一定響應(yīng); 不是只有一個(gè)固定響應(yīng), 其它負(fù)責(zé)傳遞; 歸根結(jié)底, 是整條鏈有機(jī)的負(fù)責(zé)一個(gè)響應(yīng)一個(gè)請(qǐng)求, 而不是一個(gè)鏈表中某一對(duì)象一個(gè)人在戰(zhàn)斗, 職責(zé)是鏈的職責(zé)而不是某一對(duì)象的職責(zé). 否則這條鏈除了因?yàn)閭鹘y(tǒng)鏈表的傳遞而增加了負(fù)擔(dān)又有何意義?
什么時(shí)候該用職責(zé)鏈, 請(qǐng)自己思考, 但我們完全可以做出什么時(shí)候不要使用職責(zé)模式的判斷:
1、當(dāng)一個(gè)方法的傳入?yún)?shù)將成為分支語(yǔ)句的判斷條件時(shí); 2、當(dāng)每一個(gè)分支的職責(zé)相對(duì)獨(dú)立,且邏輯較為復(fù)雜時(shí); 3、當(dāng)分支條件存在擴(kuò)展的可能時(shí)。
總而言之, 只要你看見(jiàn)"分支"二字, 請(qǐng)不要使用職責(zé)模式.
我們深入一下這個(gè)問(wèn)題的實(shí)質(zhì)的話(huà), 就會(huì)發(fā)現(xiàn), 因?yàn)檎l(shuí)對(duì)誰(shuí)響應(yīng)這個(gè)對(duì)應(yīng)關(guān)系, 是在運(yùn)行之前就確定的, 所以最適合的方式是使用表模式. 由此我們可以得出:
怪怪設(shè)計(jì)模式第一推廣: 可以找出對(duì)應(yīng)關(guān)系的, 優(yōu)先考慮表模式; 看見(jiàn)"分支"二字, 優(yōu)先考慮策略模式.
最后我們?cè)倏匆幌略髡叩淖罴褜?shí)踐:
引用(錯(cuò)誤! 見(jiàn)后)1、應(yīng)盡量將職責(zé)鏈模式的抽象定義為抽象類(lèi),而不要定義為接口。這樣有利于一些公共邏輯的重用。 2、應(yīng)在實(shí)現(xiàn)職責(zé)鏈模式的同時(shí),提供創(chuàng)建職責(zé)鏈的工廠類(lèi)。 關(guān)于1, GoF當(dāng)初是抽象類(lèi), 但是其實(shí)接口并不是不可以. 如果你想細(xì)究這個(gè)問(wèn)題, 可以看上面關(guān)于GoF例子瑕疵的討論, 來(lái)思考父類(lèi)集中操作有多大意義. 但記住請(qǐng)先翻DP95, 了解清楚那個(gè)例子. 有沒(méi)有抽象類(lèi), 關(guān)鍵還在于抽象類(lèi)合不合適:
怪怪第一哲學(xué)定律: 一切決定都是交換.
如果你公共操作很多, 那你就付出抽象類(lèi)站位的代價(jià)(提示:DP95的代碼是C++, 可以多繼承); 如果你的其它設(shè)計(jì)需要基類(lèi), 就要付出打字的代價(jià). 這兩者可以兼得嗎? 答案見(jiàn)文章末尾注釋一.
關(guān)于2, 完全不靠譜. DP95上說(shuō)的非常清楚, 職責(zé)鏈要解決的一個(gè)問(wèn)題就是, 鏈條上單元的動(dòng)態(tài)改變. 拿上面的Widget的例子來(lái)說(shuō), 難道我們應(yīng)該為每種不同排列順序, 都搞個(gè)工廠方法? 不同類(lèi)別的Widget們只要能互相嵌套, 其排列組合有多少是可以預(yù)期的. 另外, 真正的職責(zé)鏈, 接收請(qǐng)求的對(duì)象可以是鏈條中的任何一個(gè)具體子類(lèi), 而用了Factory, 我們只能從頭部傳入信息, 職責(zé)鏈原本具有的行為就不正常了, 討論見(jiàn)上面Widget那部分.
在文章末尾, 我想嚴(yán)肅的說(shuō)兩句多余的話(huà): 我一直在呼吁的是, 如果作者們不確定自己的理解, 請(qǐng)考慮對(duì)博文讀者的影響有多大可能性是壞的; 寫(xiě)東西時(shí), 也不要太過(guò)自信, 如金科玉律; 不如以提問(wèn)的方法說(shuō), 比如: "我是這樣想的, 大家看對(duì)不對(duì)? "; 更不要著急在得到驗(yàn)證前, 隨便將自己的文章放傳播到更廣泛的地方上去, 把炸彈變成原子彈.
不過(guò)我仍然覺(jué)得, 今天這塊板磚有點(diǎn)太大了, 因?yàn)槲夷靡槐驹O(shè)計(jì)模式書(shū)籍的作者當(dāng)作了例子. 我想這篇文章是dudu都不愿看到的(小人之心度君子之腹), 畢竟該作者的大作屬于博客園系列. 我其實(shí)非常猶豫發(fā)不發(fā)該文, 因?yàn)檎f(shuō)實(shí)在的, 別人對(duì)設(shè)計(jì)模式會(huì)不會(huì)誤解, 和我一樣的初學(xué)者會(huì)不會(huì)受誤導(dǎo), 跟我有屁關(guān)系? 我又不是以技術(shù)宣傳為生存手段的, 況且如果我打算寫(xiě)書(shū), 恐怕這篇文章連出版商都不愿意看到; 恐怕他們更愿意的是別人自己寫(xiě)自己的, 別涉及他們的作者的文章; 哪個(gè)出版商會(huì)對(duì)沒(méi)事得罪人的人感興趣? 我之所以意識(shí)到這些問(wèn)題, 卻沒(méi)有去換個(gè)例子, 也有考慮: 我一直提倡的是, 無(wú)論誰(shuí)寫(xiě)的是黑紙白字幾十塊一本, 還是隨便討論瞎說(shuō)幾句, 大家最好都自己思考后再說(shuō); 從這個(gè)意義上講, 原文作者的情況作為例子倒是最合適的.
且不說(shuō)招一大片人反感的問(wèn)題, 作為一個(gè)軟件從業(yè)人員和潛在競(jìng)爭(zhēng)對(duì)手, 對(duì)我來(lái)說(shuō), 別人走越多彎路, 是不是對(duì)我越有益呢? 我偶爾會(huì)思考這個(gè)問(wèn)題, 也希望得到大家的答案.
---------------------------------------- 注釋一: 我的答案是可以; 這需要額外的學(xué)習(xí)和外部條件, 和引入一個(gè)新模式, Gasket, 即墊片模式; 這個(gè)模式也要交換從而付出很大的代價(jià):該模式有擴(kuò)散的特性, 導(dǎo)致你寫(xiě)程序方方面面有徹底的變化, 甚至是沒(méi)有接觸過(guò)的方式; 所以也要下功夫?qū)W很多新東西.
P.S. 不用Baidu和Google了, 該模式是我自?shī)首詷?lè)的一個(gè)野路子, 在經(jīng)過(guò)我自己嚴(yán)格檢驗(yàn)前, 不打算發(fā)表(而且對(duì)于很多固執(zhí)的人而言肯定不愿意付出某些代價(jià)去交換), 先賣(mài)個(gè)關(guān)子吧.
|