使用設計模式改善程式結構(三)

azz發表於2007-08-23
使用設計模式改善程式結構(三)[@more@]  設計模式在某種程度上確實能夠改善我們的程式結構,使設計具有更好的彈性。也正是由於這個原因,會導致我們可能過度的使用它。程式結構具有過度的、不必要的靈活性和程式結構沒有靈活性一樣都是有害的。本文將分析過度的靈活性可能造成的危害,並且結合一些例項來闡述使用設計模式改善程式結構應遵循的原則。

  1、介紹
  本系列文章的前兩篇主要講述瞭如何使用設計模式來改善我們的程式結構,大家可以看到經過調整的程式碼具有了更大的彈性,更容易適應變化。讀者朋友可能也具有類似的經驗,透過使用設計模式使得自己的軟體系統更加具有可擴充套件性和健壯性。但是,這樣就可能會造成一個結果:無論遇到任何問題,我們首先做的就是設法找到一個解決它的設計模式來,而不是解決問題的最簡潔的方法。

  上面所述的就是過分使用設計模式的情況,它賦予了程式碼過度的靈活性。大家往往對於僵化、拙劣的設計所導致的危害非常清楚,但是對於過度靈活的設計可能帶來的危害卻不是很重視。本文試圖從這個角度來談談使用設計模式改善程式結構應遵循的原則,使大家避免陷入過分使用設計模式的狀況。其中的一個關鍵議題就是:我們為什麼要使用設計模式,到底什麼樣的程式結構才是好的。

  2、過分設計的危害
  正是由於大家對於僵化的設計所造成的結果的恐懼,以及對於設計模式給我們的程式結構帶來的無比的彈性的讚歎,才會導致過分的預先設計(up-front design)。原因很簡單:需求肯定是要變化的。所以,我們就需要給程式碼一些更多的靈活性,使得它可以適應以後的變化。於是,我們在最開始的設計中,就針對需求的變化做了很多的假設,並把對於這些假設的支援放在程式碼中。

  如果對這些假設的預測是正確的,那麼做的這一切都是值得的。不幸的是,對這些假設的預測很難是正確的。原因很簡單:需求是我們的客戶(一般是另外一個企業)提出的,但是作為一個現代的企業,要想生存,就要不斷的改變自己以適應日新月異的變化,所以客戶的需求肯定是要根據自身生存、發展的需要而不斷變化的,並且這些變化都是很難預測的,常常是客戶自己都不知道下一步該如何變化(如果都能夠被你預測到的話,這個公司肯定會高薪聘請你去做他們的CEO)。

  如果預測是錯誤的話,第一個直接後果就是,浪費了寶貴的時間、資金。我們花了很多的時間在一些根本沒有任何用處的靈活性上,而這些時間本可以用來為系統增加新的功能或者修正錯誤。

  過分靈活的程式碼往往更加複雜、難以理解。其他的開發人員不得不花費很多的時間來理解這些本來可以去除的複雜性。必然導致程式碼的維護、擴充套件困難(如果需求的變化和你的假設不同),專案的開發效率降低。

  例如:發現一種計算有多個不同的方式, 不加思索就直接採用Strategy設計模式,而不是採用簡單、清楚的條件表示式的方法(if-else語句),那麼就會導致結構的複雜(要增加好幾個類)。如果後來發現根本就沒有在增加新的計算方法方面的需求,或者更糟糕的是需求的變化是某幾個計算策略間要增加依賴關係,那麼修改起來就會十分困難。系統中如果存在太多的這種沒有必要的靈活性,很可能最終的程式結構就會陷入冗餘、混亂之中。

  程式結構的靈活性是有代價的,這種代價往往是更多的複雜性或者造成系統不容易理解,需要我們在設計時進行權衡。

  3、軟體開發的節奏
  現在在軟體工程領域很活躍的一個組織是:敏捷社團,他們提出了一系列的敏捷方法(XP就是其中很著名的一種)。在敏捷方法中制定了一系列的策略、實踐來擁抱需求的變化。其目標是:使軟體以規範的節奏進展,最終保質、按時交付軟體產品。現在已有很多使用這種方法成功的商業案例。

  對於軟體開發的節奏可以描述如下:首先寫測試程式碼,提出對於系統的功能需求,然後寫工作程式碼滿足這個需求,重複這個過程直到實現系統的所有需求。在這個過程中間要頻繁的(一般是在增加新功能或者修正錯誤時)進行重構,去除冗餘的、含糊的程式碼,改善程式的結構,使得新功能的新增變得容易。可以看出在這樣的軟體開發節奏中,沒有對需求的變化做什麼預測(進行預測的主要原因是恐懼變化),而是以一種主動的姿態來擁抱變化,當目前的設計不能夠適應發生的變化時,大膽的進行重構(因為有頻繁測試保證,所以重構的風險是不大的)去適應新的變化。這種方式被稱為演化設計(evolutionary design),在參考文獻〔3〕中可以得到進一步的內容。

  設計模式一般會成為重構的目標,但是為什麼要這樣做?我們一定要重構到一個設計模式嗎?怎樣的程式結構才是好的呢?

  4、關鍵是要展現設計意圖
  現在有一個普遍的誤解就是:程式結構的靈活性越高越好,所以對程式結構改善的目標就是使它具有更高的彈性,這樣在未來需求發生變化時可以很容易的改變程式來適應這種變化。其實,結構的靈活性和結構的易更改性之間是有矛盾的。很明顯,結構越靈活相應的就會越複雜,越複雜就越不容易理解,不容易理解怎麼會容易更改呢?參考文獻〔1〕中專門探討了這個問題,有興趣可以看看。

  正是這個誤解的存在,使得很多開發者看到了設計模式所帶給程式結構的靈活性,從而在進行程式碼重構時就把結構的靈活性作為一個最重要的目標。最終導致了程式結構的過分靈活性,損傷了軟體的質量。

  但是,設計模式確實能夠改善我們的程式結構,物件導向大師Martin Fowler的經典著作《Refactoring Improving the Design of Existing Code》一書中就有很多的使用設計模式進行重構的例子。難道僅僅是因為設計模式能夠帶來足夠的靈活性嗎?顯然不是!主要是因為這些設計模式更能夠展示設計者的設計意圖,更加便於理解!

  很多物件導向專家和模式研究專家對重構的動機進行了研究,發現對於一個好的重構過程來說,重構的結果到底是不是一個設計模式是不重要的。它們的最終動機都是:減少或者消除冗餘程式碼,簡化設計最終達到展示真正的設計意圖,更加便於交流。

  Martin Fowler的《Refactoring》一書的第一章有一個關於影碟出租的例子,詳細的展示了重構的過程以及每一步的動機,很好的說明了上面的問題。

  5、再談設計模式的動機
  本系列文章的第一篇中談論設計模式本身的意圖、動機的重要性。這裡我想再結合上面的內容重新認識一下這個問題。現在我們有兩個動機存在,設計模式本身的動機以及我們要重構來改善我們的程式結構的動機(可能是要重構到一個設計模式)。這兩個動機其實是沒有很大的關係的。

  設計模式本身的動機往往是領域無關的,但是我們重構到設計模式的動機卻往往是領域相關的。因為我們重構的主要目標是要達到能夠很好的展示我們的設計意圖,而這些設計意圖往往和問題領域的上下文關係密切。

  比如:Factory Method設計模式的意圖是定義一個建立物件的介面,讓子類來確定到底例項化哪個類,Factory Method方法使得一個類的例項化時機延遲到子類中。但是我們在重構的過程中何時決定使用Factory Method設計模式呢?基本上是因為一個類的構造方式有多種,每一種都有不同的含義,但是建構函式的名字是唯一的,透過同樣的建構函式名,賦予不同的引數的方法來例項化物件的方式,使得使用者很難理解這個建構函式的真正含義。所以透過使用Factory Method設計模式,定義不同的用於展示具體意圖的函式名稱來例項化物件,就更加能夠展示使用者的意圖。另外,該模式還簡化了該類的構造方式,封裝了該類的例項化細節。

  參考文獻〔2〕中有很多這方面的例項,大家可以下來閱讀一下,相信會有更大的收穫。

  6、結論
  本文結合設計模式探討了在進行程式結構改善的過程中,容易造成的誤解:即過分的關注程式結構的靈活性。這樣做很容易從是否能夠給設計帶來靈活性的角度來進行程式結構的重構,最終可能造成系統的複雜、混亂、不易理解、難以維護,從而導致專案失敗。其實一個好的程式結構根本不在於其中使用了多少設計模式、多麼具有彈性,主要在於該結構是否能夠非常清晰的展現設計者的意圖(可以參考參考文獻〔1〕)。由於在很多情況下,透過引入設計模式可以很好的做到這一點,所以設計模式往往會成為重構的目標,但是有一點要記住:不要過早的引入設計模式,要讓它在重構的過程中自然浮現出來。

  參考資料
  [1] To Be Explicit,Martin Fowler, IEEE Software Vol.18 No.6
  [2] Refactoring To Patterns, Joshua Kerievsky, industriallogic.com
  [3] Is Design Dead, Martin Fowler,

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10901326/viewspace-965496/,如需轉載,請註明出處,否則將追究法律責任。

相關文章