設計模式總結

 

一、 設計模式的隱喻
     武功套路是習武的門徑。新手要一招一式地練習套路,爛熟於心之後,熟能生巧,在實戰之中即可見招拆招、運用自如——此時習武之人已從“新手”成長爲“好手”。“高手”則沒有套路,實戰之中只有自然反應,然而一招一式渾然天成、恰到好處,似有似無、無中生有。“高手”之上還有“高高手”,他們達到的境界非我等憑藉金氏武俠小說可以揣測。
    設計模式之於設計,好比套路之於武術。“新手”要一個接一個地學習模式,“好手”能夠活用模式,“高手”則沒有模式。
    設計模式的“內功”是面向對象的基本原則。這些原則是“神”,模式是“形”。高手拼的是“內功”,對面向對象基本原則有了深刻的領悟,才能用好設計模式,避免“走火入魔”。
    一般在設計模式著作的前幾章都會介紹面向對象的基本原則,這幾章非常重要。學通了這幾章,後面的模式就不過如此了。學完了設計模式,也最好翻過頭來重新看看這幾章,保證會有新的領悟。

二、 爲什麼使用設計模式
    對任何設計都可以憑主觀(對設計很難做出客觀評價)判斷得出它是一個好的設計,還是一個壞的設計。使用設計模式是爲了避免壞的設計。Martin叔叔在他的著作《敏捷軟件開發 原則、模式與實踐》中描述了拙劣設計的症狀:

  • 僵化性(Rigidity):設計難以改變。
  • 脆弱性(Fragility):設計易於遭到破壞。
  • 牢固性(Immobility):設計難以重用。
  • 粘滯性(Viscosity):難以做正確的事情。
  • 不必要的複雜性(Needless Complexity):過分設計。
  • 不必要的重複(Needless Repetition):過多的重複。
  • 晦澀性(Opacity):混亂的表達。


三、 什麼時候使用設計模式
    Martin叔叔的書中有段話:
  
    在學習它們(設計原則和模式)的時候,請記住,敏捷開發人員不會對一個龐大的預先設計應用那些原則和模式。相反,這些原則和模式被應用在一次次的迭代中,力圖使代碼以及代碼所表達的設計保持乾淨。
  
    在這段容易被讀者忽略的文字中,我體會到這樣幾層含義:

  • 代碼是設計(這是Martin叔叔強調的一個觀點,這個觀點可以參考《敏捷軟件開發 原則、模式與實踐》一書的附錄D);
  • 設計模式是爲了使設計適應變化;
  • 設計模式是重構的工具;
  • 設計一開始就要保持乾淨、簡單,以後仍然要保持乾淨、簡單;
  • 不能過度使用設計模式。


    使用設計模式的目的是爲了適應未來的變化,變化之所以存在是因爲它的不可預知性——如果可以預知,則不能稱其爲變化。如何判斷哪些需求可能變化,哪些需求可能不變,並且在最大程度上保持設計的乾淨、簡單,這是些工藝問題,而不是工程問題。既然是工藝問題,那麼就只能給出原則,不能給出標準。使用設計模式的大體原則可能是:對未來極有可能發生變化的問題給出最簡單、修改成本最低的解。
  
四、 避免過度使用設計模式
    易維護的程序首先要易理解,這一點遠甚於其他。在易理解的代碼上纔好維護。過分地使用設計模式會增加程序的複雜性和晦澀性,讓程序不易理解,從而降低了程序的易維護性。
    Switch語句曾經遭致詬病,許多重構的例子就是拿Switch開刀。我認爲Switch語句是高效的語句,可以寫出極優雅、簡單的代碼。在很多情況下,直接使用Switch語句比把它拆成若干個Class更“乾淨”。
    再比如,有一段四百多行的代碼負責整個系統的調度,如果未來的變化僅僅是修改這四百行代碼而不會大量添加代碼,那麼把這四百多行代碼集中在一個函數裏面,比將它拆分成十來個Class更加容易維護。

五、 討論幾個具體的模式
1、 創建模式(Creational Pattern)
    工廠(Factory Method)模式是常用的模式。工廠模式的應用情景明確,設計思想簡單。從使用多態到只用一個靜態方法,工廠模式的變化形式有很多。我習慣簡單地使用工廠模式,也就是使用只有靜態方法的工廠模式。下面的工廠模式代碼簡單、乾淨:
  
  MyFactory.GetClassInstance().DoFunction();
  
    類廠並不承載業務邏輯,需求變化對類廠的影響通常很小。因此使用重量級的工廠模式往往並不划算。一組包含層次關係的重量級的工廠類,可能意味着過度設計。
   單例(Singleton)模式和工廠模式關係密切。從實現的角度講,單例模式是工廠模式的一個特例,但是兩個模式的應用情景不同,因此它們屬於不同的模式。
   抽象工廠(Abstract Factory)模式是工廠模式的推廣。抽象工廠模式的應用情景更加特殊和嚴格。在一個使用抽象工廠的設計中,如果未來發生不同產品族各自演化的情形,那麼抽象工廠模式就可能崩潰了。在實際應用中,不同產品族各自演化,最終分道揚鑣的情形是有的,用戶提出這樣的需求的確讓人“觸目驚心”。在使用抽象工廠模式之前,一定要保證從現在到未來都能夠用一致的方式使用這些產品族。
   將工廠模式稍加變化可以得到建造(Builder)模式。工廠模式的“加工工藝”是隱藏的,而建造模式的“加工工藝”是暴露的。這點不同,使建造模式在更加靈活的同時也有失優雅。
2、 模板模式(Template Method)和策略(Strategy)模式
   模板模式和策略模式的應用情景類似,但實現方式不同,前者使用繼承,後者使用委託。
   模板模式有可能是最“古老”的模式之一,在使用面向對象技術的早期,“繼承”大行其道,很多設計人員可能不自覺地使用過模板模式。模板模式的缺點是把具體實現和通用算法緊密地耦合起來,使得具體實現只能被一個通用算法操縱。然而在繼承關係中,父類的信息可以更多地暴露給子類,這種(違背面向對象設計原則的)微妙的溝通在一些特定應用中顯得更加靈活和方便。
   策略模式是委託的經典用法。策略模式消除了通用算法和具體實現的耦合,使得具體實現可以被多個通用算法操縱。策略模式也增加了類層次,比模板模式複雜。
   模板模式和策略模式通常可以互相替換。它們都像試卷,模板模式是填空題,策略模式是選擇題。
3、 簡化問題的模式
    門面(Facade)模式把一組複雜的接口隱藏在一個簡單且特定的接口後面。
    調停者(Mediator)模式把對象之間的引用關係包裝在一個特定的容器裏面。
   組合(Composite)模式描述了整體與部分的結構關係,並且允許用一致的方式處理這個結構。
   上面幾個模式對使用者而言,都在一定程度上起到了簡化問題的作用。
4、 擴展功能的模式
   訪問者(Visitor)模式和裝飾(Decorator)模式都可以在不改變現有類結構的基礎上,動態地增加功能。
   訪問者模式把現有類結構上的對象“分配”到一個名爲訪問者的類中,在訪問者的相應方法中配置對象、改變對象或擴展功能。
   裝飾模式把現有類結構上的對象“注入”一個裝飾類中,在裝飾類中擴展它的功能。
   訪問者模式和裝飾模式在實際效果上是不同的。訪問者模式可以把對象分配到相應的方法裏,從而對每個對象分別進行加工或擴展。而裝飾模式只能用一致的方式對所有的被裝飾對象進行加工或擴展,要想實現不同的加工或擴展,只能增加新的裝飾類。
   過多的“裝飾類”有可能使業務邏輯分散,並且使程序結構複雜。針對每一個具體的派生類,“訪問類”都要有一個對應的方法,增加派生類的時候也要增加訪問類的方法。擴展功能的需求是經常發生的,是否有必要使用上述模式則值得再三考慮。
5、 其他常用的模式
   橋樑(Bridge)模式。Class是封裝了行爲和屬性的容器,然而Class的一組行爲可能獨立演化,這時最直接的想法是使用繼承,把各不相同的行爲封裝在不同的子類裏。橋樑模式從另外的角度解決了這個問題。橋樑模式把獨立演化的行爲封裝在另外一個類體系裏,與原來的類體系分別獨立演化,兩個類體系在抽象層次是“使用”關係。在很多OO教材裏面用Shape類封裝屬性和Draw方法,在橋樑模式裏,“形狀”和“畫筆”是兩組獨立演化的類體系,在抽象層次,“形狀”使用“畫筆”繪製自己。
適配器(Adapter)模式是常用模式,它比較簡單,有時和其他的模式配合使用。
   命令(Command)模式被Martin稱爲“最簡單、最優雅的模式之一”。命令模式的魅力在於它爲每個類“培訓”出了相同的技能,經過“培訓”的類“柔性”更強,能夠產生不可思議的能力。
6、 不太需要的模式
   觀察者(Observer)模式。Java和C# 都實現了觀察者模式。
   迭代子(Iterator)模式。在Java和C#語言裏,可以用聚集類代替。
   備忘錄(Memento)模式。可以用Class的序列化能力代替。
   責任鏈(Chain of Responsibility)模式。可以用其他的方式替代,例如觀察者模式、語言本身提供的消息機制等。
   解釋器(Interpreter)模式。個人認爲,它是個算法,不是模式。
   代理(Proxy)模式。如果在一開始就知道某些底層策略一定會被替換掉,那麼使用代理來隔離這些策略還是有必要的。否則,幾乎沒有使用的必要。
  
參考文獻:
Robert C. Martin,Agile Software Development Principles,Patterns,and Practices.(中譯本:《敏捷軟件開發 原則、模式與實踐》,鄧輝譯,清華大學出版社,2003年)

發佈了14 篇原創文章 · 獲贊 1 · 訪問量 5萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章