標籤:
活用設計模式
一、設計模式的隱喻
武功套路是習武的門徑。新手要一招一式地練習套路,爛熟於心之後,熟能生巧,在實戰之中即可見招拆招、運用自如——此時習武之人已從“新手”成長為“好手”。“高手”則沒有套路,實戰之中只有自然反應,然而一招一式渾然天成、恰到好處,似有似無、無中生有。“高手”之上還有“高高手”,他們達到的境界非我等憑藉金氏武俠小說可以揣測。
設計模式之於設計,好比套路之於武術。“新手”要一個接一個地學習模式,“好手”能夠活用模式,“高手”則沒有模式。
設計模式的“內功”是物件導向的基本原則。這些原則是“神”,模式是“形”。高手拼的是“內功”,對物件導向基本原則有了深刻的領悟,才能用好設計模式,避免“走火入魔”。
一般在設計模式著作的前幾章都會介紹物件導向的基本原則,這幾章非常重要。學通了這幾章,後面的模式就不過如此了。學完了設計模式,也最好翻過頭來重新看看這幾章,保證會有新的領悟。
二、為什麼使用設計模式
對任何設計都可以憑主觀(對設計很難做出客觀評價)判斷得出它是一個好的設計,還是一個壞的設計。使用設計模式是為了避免壞的設計。Martin叔叔在他的著作《敏捷式軟體開發 (Agile Software Development)原則、模式與實踐》中描述了拙劣設計的癥狀:
l 僵化性(Rigidity):設計難以改變。
l 脆弱性(Fragility):設計易於遭到破壞。
l 牢固性(Immobility):設計難以重用。
l 粘滯性(Viscosity):難以做正確的事情。
l 不必要的複雜性(Needless Complexity):過分設計。
l 不必要的重複(Needless Repetition):過多的重複。
l 晦澀性(Opacity):混亂的表達。
三、什麼時候使用設計模式
Martin叔叔的書中有段話:
在學習它們(設計原則和模式)的時候,請記住,敏捷開發人員不會對一個龐大的預先設計應用那些原則和模式。相反,這些原則和模式被應用在一次次的迭代中,力圖使代碼以及代碼所表達的設計保持乾淨。
在這段容易被讀者忽略的文字中,我體會到這樣幾層含義:
l 代碼是設計(這是Martin叔叔強調的一個觀點,這個觀點可以參考《敏捷式軟體開發 (Agile Software Development)原則、模式與實踐》一書的附錄D);
l 設計模式是為了使設計適應變化;
l 設計模式是重構的工具;
l 設計一開始就要保持乾淨、簡單,以後仍然要保持乾淨、簡單;
l 不能過度使用設計模式。
使用設計模式的目的是為了適應未來的變化,變化之所以存在是因為它的不可預知性——如果可以預知,則不能稱其為變化。如何判斷哪些需求可能變化,哪些需求可能不變,並且在最大程度上保持設計的乾淨、簡單,這是些工藝問題,而不是工程問題。既然是工藝問題,那麼就只能給出原則,不能給出標準。使用設計模式的大體原則可能是:對未來極有可能發生變化的問題給出最簡單、修改成本最低的解。
四、避免過度使用設計模式
易維護的程式首先要易理解,這一點遠甚於其他。在易理解的代碼上才好維護。過分地使用設計模式會增加程式的複雜性和晦澀性,讓程式不易理解,從而降低了程式的易維護性。
Switch語句曾經遭致詬病,許多重構的例子就是拿Switch開刀。我認為Switch語句是高效的語句,可以寫出極優雅、簡單的代碼。在很多情況下,直接使用Switch語句比把它拆成若干個Class更“乾淨”。
再比如,有一段四百多行的代碼負責整個系統的調度,如果未來的變化僅僅是修改這四百行代碼而不會大量添加代碼,那麼把這四百多行代碼集中在一個函數裡面,比將它拆分成十來個Class更加容易維護。
五、討論幾個具體的模式
1、建立模式(Creational Pattern)
原廠模式(Factory Method)是常用的模式。原廠模式的應用情景明確,設計思想簡單。從使用多態到只用一個靜態方法,原廠模式的變化形式有很多。我習慣簡單地使用原廠模式,也就是使用只有靜態方法的原廠模式。下面的原廠模式代碼簡單、乾淨:
MyFactory.GetClassInstance().DoFunction();
類廠並不承載商務邏輯,需求變化對類廠的影響通常很小。因此使用重量級的原廠模式往往並不划算。一組包含層次關係的重量級的工廠類,可能意味著過度設計。
單例模式(Singleton Method)和原廠模式關係密切。從實現的角度講,單例模式是原廠模式的一個特例,但是兩個模式的應用情景不同,因此它們屬於不同的模式。
抽象原廠模式(Abstract FactoryMethod)是原廠模式的推廣。抽象原廠模式的應用情景更加特殊和嚴格。在一個使用抽象工廠的設計中,如果未來發生不同產品族各自演化的情形,那麼抽象原廠模式就可能崩潰了。在實際應用中,不同產品族各自演化,最終分道揚鑣的情形是有的,使用者提出這樣的需求的確讓人“觸目驚心”。在使用抽象原廠模式之前,一定要保證從現在到未來都能夠用一致的方式使用這些產品族。
將原廠模式稍加變化可以得到建造模式(Builder Method)。原廠模式的“加工工藝”是隱藏的,而建造模式的“加工工藝”是暴露的。這點不同,使建造模式在更加靈活的同時也有失優雅。
2、模板模式(Template Method)和策略模式(Strategy Method)
模板模式和策略模式的應用情景類似,但實現方式不同,前者使用繼承,後者使用委託。
模板模式有可能是最“古老”的模式之一,在使用物件導向技術的早期,“繼承”大行其道,很多設計人員可能不自覺地使用過模板模式。模板模式的缺點是把具體實現和通用演算法緊密地耦合起來,使得具體實現只能被一個通用演算法操縱。然而在繼承關係中,父類的資訊可以更多地暴露給子類,這種(違背物件導向設計原則的)微妙的溝通在一些特定應用中顯得更加靈活和方便。
策略模式是委託的經典用法。策略模式消除了通用演算法和具體實現的耦合,使得具體實現可以被多個通用演算法操縱。策略模式也增加了類層次,比模板模式複雜。
模板模式和策略模式通常可以互相替換。它們都像試卷,模板模式是填空題,策略模式是選擇題。
3、簡化問題的模式
門面模式(Façade Method)把一組複雜的介面隱藏在一個簡單且特定的介面後面。
調停者模式(Mediator Method)把對象之間的參考關聯性封裝在一個特定的容器裡面。
組合模式(Composite Method)描述了整體與部分的結構關係,並且允許用一致的方式處理這個結構。
上面幾個模式對使用者而言,都在一定程度上起到了簡化問題的作用。
4、擴充功能的模式
訪問者模式(Visitor Method)和裝飾模式(Decorator Method)都可以在不改變現有類結構的基礎上,動態地增加功能。
訪問者模式把現有類結構上的對象“分配”到一個名為訪問者的類中,在訪問者的相應方法中設定物件、改變對象或擴充功能。
裝飾模式把現有類結構上的對象“注入”一個裝飾類中,在裝飾類中擴充它的功能。
訪問者模式和裝飾模式在實際效果上是不同的。訪問者模式可以把對象分配到相應的方法裡,從而對每個對象分別進行加工或擴充。而裝飾模式只能用一致的方式對所有的被裝飾對象進行加工或擴充,要想實現不同的加工或擴充,只能增加新的裝飾類。
過多的“裝飾類”有可能使商務邏輯分散,並且使程式結構複雜。針對每一個具體的衍生類別,“訪問類”都要有一個對應的方法,增加衍生類別的時候也要增加訪問類的方法。擴充功能的需求是經常發生的,是否有必要使用上述模式則值得再三考慮。
5、其他常用的模式
橋樑模式(Bridge Method)。Class是封裝了行為和屬性的容器,然而Class的一組行為可能獨立演化,這時最直接的想法是使用繼承,把各不相同的行為封裝在不同的子類裡。橋樑模式從另外的角度解決了這個問題。橋樑模式把獨立演化的行為封裝在另外一個類體系裡,與原來的類體系分別獨立演化,兩個類體系在抽象層次是“使用”關係。在很多OO教材裡面用Shape類封裝屬性和Draw方法,在橋樑模式裡,“形狀”和“畫筆”是兩組獨立演化的類體系,在抽象層次,“形狀”使用“畫筆”繪製自己。
適配器模式(Adapter Method)是常用模式,它比較簡單,有時和其他的模式配合使用。
命令模式(Command Method)被Martin稱為“最簡單、最優雅的模式之一”。命令模式的魅力在於它為每個類“培訓”出了相同的技能,經過“培訓”的類“柔性”更強,能夠產生不可思議的能力。
6、不太需要的模式
觀察者模式(Observer Method)。Java和C#都實現了觀察者模式。
迭代子模式(Iterator Method)。在Java和C#語言裡,可以用聚集類代替。
備忘錄模式(Memento Method)。可以用Class的序列化能力代替。
責任鏈模式(Chain of ResponsibilityMethod)。可以用其他的方式替代,例如觀察者模式、語言本身提供的訊息機制等。
解譯器模式(Interpreter Method)。個人認為,它是個演算法,不是模式。
代理模式(Proxy Method)。如果在一開始就知道某些底層策略一定會被替換掉,那麼使用代理來隔離這些策略還是有必要的。否則,幾乎沒有使用的必要。
參考文獻:
1.Gamma etc., DesignPatterns: Elements of Reusable Object-Oriented Software. Addison-Wesley1995.(中譯本:《設計模式:可複用物件導向軟體的基礎》,李英軍等譯,機械工業出版社,2000年)
2.Robert C. Martin,Agile SoftwareDevelopment Principles, Patterns, and Practices.(中譯本:《敏捷式軟體開發 (Agile Software Development)原則、模式與實踐》,鄧輝譯,清華大學出版社,2003年)
張昱 曾就職於浪潮集團、聯想集團,現就職於中科院電子所。超過十年的軟體工作經驗。對分析設計、專案管理、編程實踐有著濃厚的興趣。
--------------------------------------------------------------
參考:
《tLEE——設計模式》
《yyyuhan——C++設計模式》
《MaoBisheng——設計模式》
《Pluginsin C++》
《Buildinga Better Plugin Architecture(C++)》
《BuildingYour Own Plugin Framework》
軟體設計模式