前三篇,嘎嘎。
設(shè)計(jì)模式小結(jié)(三)
設(shè)計(jì)模式小結(jié)(二)
設(shè)計(jì)模式小結(jié)(一)
Mediator/Observer:描述:將復(fù)雜的組件間交互集中到Mediator中/將復(fù)雜組件的交互用Observer - Subject相分離。
動(dòng)機(jī):軟件的復(fù)雜之處就在于處理各個(gè)組分之間的聯(lián)系。處理聯(lián)系一般有兩種方式,第一種是將相對(duì)獨(dú)立的點(diǎn)進(jìn)行有效的劃分和隔離,減少軟件之間的聯(lián)系總數(shù),降低復(fù)雜度。但是如果這些點(diǎn)之間關(guān)系太過(guò)復(fù)雜,那么劃分是無(wú)法解決根本復(fù)雜度的,這時(shí)候,一種可選的方案是干脆將所有的聯(lián)系封裝到一點(diǎn),讓那些本該獨(dú)立的部分不至于被這些藕斷絲連拖累到一起。
對(duì)于大多數(shù)模式而言,顯然前者是比較理想的選擇。而Mediator幾乎就是23.5個(gè)設(shè)計(jì)模式里面的特例,它的指導(dǎo)思想是將聯(lián)系及其在軟件生命周期中的變化集中到一處以方便管理。
用法:用于維護(hù)多個(gè)狀態(tài)的一致性。
Mediator和Observer通常可換用。Mediator更加適用的情況:當(dāng)操作本身出現(xiàn)強(qiáng)關(guān)聯(lián)性而不是在數(shù)據(jù)上出現(xiàn)關(guān)聯(lián)性,比方說(shuō)一些消息的連鎖觸發(fā)。當(dāng)然也可以把這一觸發(fā)過(guò)程建模成事件后看做是狀態(tài)的更改而啟用Observer模式。
如果需要協(xié)調(diào)的類是不可更改的,且高度復(fù)雜或難于擴(kuò)充,如第三方GUI控件,那么通常也會(huì)使用Mediator而不是Observer。
想用Observer?當(dāng)然也可以,如果你愿意使用Adapter,Decorator或者Proxy等模式將目標(biāo)控件封裝起來(lái),自然也是可以做到的。
更新操作需要高度集中或者需要高度優(yōu)化時(shí),需要選擇Mediator,這也是緊耦合的擅長(zhǎng)之處。
Observer的優(yōu)勢(shì)在于可以輕松的用單向連接或輪詢的方式完成表現(xiàn)層到模型層的交互,因此對(duì)于Web一類有特殊限制的應(yīng)用,Observer要更加適用一些。
Memento:對(duì)于多數(shù)系統(tǒng)而言,這一設(shè)計(jì)幾乎是一定可以見(jiàn)到的。Memento的變種頗多,比如序列化/反序列化,持久化/反持久化,實(shí)際上都是完成同樣的工作。
應(yīng)用:
Memento自身是需要進(jìn)行再分層并復(fù)用的。通常的Memento分為上下兩層,下層與存儲(chǔ)設(shè)備接駁,負(fù)責(zé)實(shí)際的存儲(chǔ)工作。多數(shù)語(yǔ)言中的存儲(chǔ)流均可以作為這一層中。在這一層需要為存儲(chǔ)的物理介質(zhì)提供一定程度的抽象,這樣我們可以不用關(guān)心數(shù)據(jù)流究竟是被存儲(chǔ)到內(nèi)存中還是數(shù)據(jù)庫(kù)或者網(wǎng)絡(luò)服務(wù)器上。上層則提供了數(shù)據(jù)對(duì)象的語(yǔ)義,這樣可以為客戶對(duì)象提供一個(gè)友善的接口,同時(shí)這一抽象可以用于滿足其他的需求,如緩存和代理。
Memento有兩種典型的存儲(chǔ)方式,
一種是為一類對(duì)象提供固定的存儲(chǔ)模式,這種設(shè)計(jì)可以最小化數(shù)據(jù)流量,因而可以獲得良好的性能,缺陷在于與對(duì)象耦合過(guò)于緊密,在面臨對(duì)象修改的時(shí)候靈活性有著很大的不足,
另一種設(shè)計(jì)是基于查詢表的理念。這種方法靈活性較高,可以使同樣的數(shù)據(jù)以多個(gè)角度展現(xiàn),缺點(diǎn)在于會(huì)遇到類型轉(zhuǎn)換的問(wèn)題,存儲(chǔ)效率上較差。代碼的自描述性弱,需要有文檔進(jìn)行約定。
在實(shí)際運(yùn)用中,Memento模式面臨的一大問(wèn)題在于對(duì)內(nèi)嵌引用的存儲(chǔ)問(wèn)題。如果可以保證存儲(chǔ)與讀取時(shí)被保存對(duì)象持有的引用不發(fā)生任何可見(jiàn)的變化,那么可以直接保存引用本身(例如C++中的指針地址)。
如果被存儲(chǔ)的對(duì)象所持有的內(nèi)嵌引用可以用值進(jìn)行替換(也就是不共享),那么可以選擇將引用也嵌入式的序列化出來(lái)。如果需要將被共享的嵌入對(duì)象序列化,那么則需要顯式的將內(nèi)嵌的每一個(gè)獨(dú)立的引用標(biāo)識(shí)成一個(gè)唯一的句柄。在
序列化時(shí),依次的對(duì)象進(jìn)行序列化,并對(duì)序列化的對(duì)象用句柄標(biāo)識(shí)。在序列化對(duì)象時(shí)如果遇到了內(nèi)嵌的引用,則用它的句柄填充到引用的位置上。在
反序列化時(shí),第一步是先將各個(gè)句柄反序列化成對(duì)象,填充上所有的值域,并重建句柄表,再利用句柄表將各個(gè)對(duì)象中的句柄用實(shí)際引用進(jìn)行替換。
這一方法的典型運(yùn)用包括運(yùn)行狀態(tài)的snapshot(例如游戲的讀存盤)。
對(duì)于固定初始產(chǎn)生固定序列的系統(tǒng),如偽隨機(jī),則可以保存它的初始值,并使用過(guò)程化的方法產(chǎn)生序列以恢復(fù)現(xiàn)場(chǎng)。但是這里需要注意,
多線程的系統(tǒng)會(huì)加大對(duì)象恢復(fù)的難度,必須要仔細(xì)的考慮,例如僅在線程Join的地方,或線程固定的懸掛點(diǎn)上進(jìn)行序列化工作。
此外,由于Memento經(jīng)常保存并恢復(fù)完整對(duì)象,因此可以將Memento與Deepcopy統(tǒng)一設(shè)計(jì)實(shí)現(xiàn)。
例如運(yùn)用DeepCopy保存當(dāng)前對(duì)象的狀態(tài),并在恢復(fù)狀態(tài)時(shí)直接用副本替換當(dāng)前對(duì)象,或者將Deepcopy用Memento實(shí)現(xiàn),可以實(shí)現(xiàn)對(duì)象的遠(yuǎn)程復(fù)制或狀態(tài)同步。
State/Strategy:State和Strategy的區(qū)別很小,他們的差別往往并非來(lái)自與程序上的差異而是在觀點(diǎn)上的差異性。
個(gè)人認(rèn)為,
和State和Strategy類似的還有Abstract Factory以及Factory Method模式。之所以我將這幾個(gè)模式綁定在一起進(jìn)行討論,本質(zhì)上是因?yàn)樗麄兌紝⒎稚⒃诔绦蚋魈幍姆种袛嗉械揭惶庍M(jìn)行管理。
如果將這三個(gè)模式進(jìn)行進(jìn)一步的抽象,可以總結(jié)出它們共同的工作方式:
獲得上下文->獲得前置條件并選擇分支->執(zhí)行分支->設(shè)置后置條件->重設(shè)上下文。利用這個(gè)工作方式,下面簡(jiǎn)單的分析一下這三個(gè)模式各自的異同點(diǎn)以及適用面。
AF工作的理想情況是,上下文的獲取、前置條件的設(shè)置、分支的選擇放在一起完成。也就是說(shuō)在一個(gè)執(zhí)行期很靠前的地方就完成了產(chǎn)品線的設(shè)置,并且在很長(zhǎng)一段運(yùn)行期中不做更改。分支的執(zhí)行通常比較Lazy,也很分散。后置條件和上下文設(shè)置不存在。
在其他工作條件下,
這一模式可能會(huì)誘發(fā)三個(gè)負(fù)面問(wèn)題。第一,是如果產(chǎn)品的創(chuàng)建分散,那么便需要仔細(xì)的跟蹤程序的執(zhí)行流程以確定到底哪一套產(chǎn)品線被構(gòu)造出來(lái);其次,如果產(chǎn)品系列間的區(qū)分不是依靠類型信息而是某個(gè)內(nèi)含的狀態(tài)進(jìn)行區(qū)分,會(huì)加大對(duì)產(chǎn)品線判斷的難度。第三,如果產(chǎn)品系列中對(duì)應(yīng)的產(chǎn)品的構(gòu)造接口存在這某種語(yǔ)義上的不一致性,或者對(duì)應(yīng)產(chǎn)品本身的接口存在不一致性,都會(huì)使得程序的可讀性大大下降。
因此Abstract Factory需要盡可能早的確定產(chǎn)品系列,同時(shí)產(chǎn)品系列在確定以后盡可能少做修改。如果產(chǎn)品線確定的很遲,那么最好在使用前再進(jìn)行創(chuàng)建,并將Abstract Factory的創(chuàng)建活動(dòng)放在它的產(chǎn)品的管理層上(例如XXXManager或XXXCollection這樣的)。
State和Strategy的情況較為類似,這個(gè)工作流程中,各個(gè)工序可能很緊湊(例如FSM,有窮狀態(tài)機(jī)),也有可能很分散。同時(shí)由于這一工作流程上下關(guān)系緊密,因此各道工序的合并或分離存在多種方案。下面將分析一下State模式。
如果將State的調(diào)用方看作State Machine,那么State變化有兩類主要的觸發(fā)者,一類來(lái)自于State Machine內(nèi),一類是來(lái)自于State Machine外的客戶代碼。實(shí)際上真正需要再State Machine內(nèi)完成狀態(tài)切換操作,基本上都是State之間的相互切換。就是說(shuō)State本身是狀態(tài)切換的觸發(fā)者,這里簡(jiǎn)寫為State-Triggered Mode(STM)。其他的狀態(tài)切換的原因基本上都來(lái)自于外部,這里簡(jiǎn)寫為Client-Triggered Mode(CTM)。
在設(shè)計(jì)State時(shí),盡可能避免STM和CTM同時(shí)存在。一旦同時(shí)存在,由于STM基本上是不可以被放置到State Machine之外的,因此建議將STM和CTM同時(shí)封裝在State Machine中,客戶端通過(guò)調(diào)用State Machine的對(duì)應(yīng)接口實(shí)現(xiàn)CTM。這樣可以將State Machine的切換邏輯統(tǒng)一到State中,封裝了可能的變化并提供了更高層的語(yǔ)義。
如果只存在CTM,那么State Machine大可以將State直接以getter/setter的形式提供給客戶代碼訪問(wèn),簡(jiǎn)化了客戶調(diào)用,也能明確客戶的職責(zé)。
同時(shí),在存在STM的時(shí)候,后置條件和上下文的設(shè)置,即狀態(tài)切換既可以放在State中,也可以放在State Machine中。在State中直接切換狀態(tài)很直接,同時(shí)可以在State需要經(jīng)常修改并改變其后繼狀態(tài)的時(shí)候很管用,對(duì)于很復(fù)雜的狀態(tài)跳轉(zhuǎn)規(guī)則它也可以Work Well,和State Machine的依賴較小,僅僅需要通知State Machine切換到哪個(gè)狀態(tài)上就可以了。同時(shí),一旦狀態(tài)機(jī)中添加或者刪除了狀態(tài),那么也會(huì)使得修改擴(kuò)散到很多地方。
如果在State Machine中進(jìn)行狀態(tài)切換,那么便需要State Machine對(duì)State有一定的了解,并且做出正確地決策。如果跳轉(zhuǎn)規(guī)則很復(fù)雜,那么State Machine就會(huì)變得很麻煩,甚至可能導(dǎo)致State Machine變成一盤大的spaghetti。但是對(duì)于如果需要經(jīng)常添加/刪除State,或者State跳轉(zhuǎn)規(guī)則多但簡(jiǎn)單的時(shí)候,在State Machine中維護(hù)狀態(tài)切換會(huì)更方便一些。
好吧,我承認(rèn)這個(gè)和Observer/Mediator一樣,兩者都很Perfect,如果你的運(yùn)氣夠好,你的選擇又正確的話。
如果問(wèn)題本身就很困難,那就請(qǐng)不要過(guò)多的苛責(zé)設(shè)計(jì),No Silver Bullet嘛。Good Luck,或者重新進(jìn)行需求分析。
Template Method
Template Method是一個(gè)有點(diǎn)兒違背面向?qū)ο笤瓌t的模式。它雖然簡(jiǎn)化了代碼結(jié)構(gòu),但是由于將一致性的維護(hù)分散在了子類和父類兩個(gè)不同的部分,提高了代碼的閱讀難度。同時(shí)這也是一個(gè)實(shí)現(xiàn)繼承而不是接口繼承的例子。面向?qū)ο笤瓌t這個(gè)東西究竟只是原則,實(shí)際應(yīng)用的時(shí)候,也是會(huì)有一定程度的變通。當(dāng)然這變通也要付出代價(jià)的。雖然我不喜歡Template Method,但是仍然會(huì)經(jīng)常的使用它,因?yàn)槲易裱璌ent Beck大牛的懶惰是程序員的美德這一教導(dǎo),同時(shí)也是因?yàn)閳?zhí)迷于Occam剃刀的美感。
Visitor
其實(shí)我還真不太明白,如果我需要Visitor的時(shí)候,還有什么更好的選擇。并且,如果在遍歷對(duì)象的時(shí)候無(wú)后效性的話(就是對(duì)其它對(duì)象的操作和當(dāng)前對(duì)象的操作沒(méi)什么關(guān)系,遍歷結(jié)果與順序無(wú)關(guān)),做起Visitor來(lái),會(huì)輕松許多的。
-----------------------------------------------------------------------------------------------------
全文終于完結(jié)了。雖然是“泛泛而談”,但是我覺(jué)得這個(gè)“泛泛”已經(jīng)比較詳細(xì)了。
另外,有些觀點(diǎn)可能GOF上面已經(jīng)有所闡明,不過(guò)我相信大部分應(yīng)該還是沒(méi)有的。
如果還有什么GOF上說(shuō)的我重復(fù)了的話,那我只能贊嘆GOF太博大精深了,以至于我無(wú)論看幾遍都會(huì)對(duì)其中的觀點(diǎn)有所遺漏。
最后祝大家在Design Pattern中游得愉快。