換種思路去理解設計模式(中)

王福朋發表於2014-05-24

繼上一篇換種思路去理解設計模式(上)繼續講。如果沒有看過上一上一篇部落格,請先點選看看。要不然直接看這篇會找不著頭緒。

7   多個物件組成結構

7.1     過程描述

  上一節介紹瞭如何建立一個物件。但大多數情況,一個物件是不夠用的,這時候就需要把物件包裝、封裝、多物件組合。有時候還需要將一個組合作為一個整體使用,組合要提供對外的介面,也可能會用到系統原有的介面。

  下面針對每種情況詳細介紹。

7.2     情況1:借用外部介面

  有開發經驗的人知道,日常大部分開發都是在已有系統基礎上開發的。即便是從新開發的系統,也要依賴於一個框架或者庫。

  所以,我們每時每刻都在用系統已有的介面。但是如果這些介面不滿足我們的需求,我們就需要重新對介面封裝一下,讓其符合當前的規則。就是這個我們日常用的技巧,被GoF總結成為一個模式——介面卡模式

  不用看程式碼和類圖,也能明白它的意思。不必太計較程式碼和類圖的細節,重點在於理解設計思想。

  顧名思義,介面卡就是做一個隔離,起到了解耦的作用。例如我們日常用的膝上型電腦介面卡。

7.3   情況2:給物件增加新功能

  系統總是在不斷的維護和升級當中,也可能在不斷的需求變更當中,因此為物件增加新功能,是再常見不過的了。那麼如何為物件增加新功能呢?

  最直接的回答就是改程式碼唄。改型別的程式碼,增加方法、屬性等。

  

  對於這種修改,首先想到的應該是違反了“開放封閉原則”和“單一職責原則”,違反這種原則帶來的壞處很多。程式碼越改越多;每次更改都有可能影響以前程式碼;多人維護一個檔案,不利於協同開發……

  

  如果用“抽象”“隔離”的思想來思考這一問題,很容易就能找出思路:第一,把原有功能和新增功能隔離;第二,兩者都依賴於一個抽象,這個抽象就是物件應該有的所有功能;第三,外部客戶將依賴於抽象,它不會察覺內部的變化(依賴倒置原則)。

  這就是裝飾模式

  

  從上面的類圖看,ConreteComponent是原始型別,Decorator是一個抽象類,它的派生類負責新增新功能。這裡理解的重點,在於Decorator類中有一個Component屬性,相當於Decorator封裝了一個Component,直接呼叫他原有的功能,並且可以新增功能。當然,這些操作都是可以派生在子類中實現的。而且不同的子類可以實現增加不同的功能。

  這樣的抽象和分離就符合開放封閉原則和單一職責原則,也不會出現程式碼量過多、多人維護不便等問題。

7.4    情況3:封裝功能

  對於有些功能,我們不希望客戶端直接呼叫,而是在呼叫時候先做一個判斷,或者加一個快取。其實就是在真實功能和客戶端之間,加一箇中間層。而這不能讓客戶端呼叫察覺。

  如果你把這個中間層直接加入到真是功能中,雖然這可以不讓客戶端察覺,那將會給系統帶來隱患,違反“單一職責原則”。如下:

  

 

  首先,如何不讓客戶端察覺?答案很簡單——依賴倒置原則——讓客戶端依賴於一個抽象。這個抽象將如何實現呢? 具體的實現和中間層都要去實現。如下:

  

   類圖如下:

  

 

  這就是代理模式

  每個設計模式要體現的都是一種設計的思路,代理模式就是要在客戶端和底層實現加一層,在該層中實現一些業務場景。4s店就是客戶於汽車廠家的代理。

  具體是否要都去實現同一個介面,這種細節不重要,不要去過於糾結這些類圖和程式碼。

7.5    情況4:遞迴關係的組合

  上文提到如何更有效率的維護物件功能和新增功能,以及更有效率的封裝物件。這兩種做法的輸出,其實還是一個單個的物件。如何將一個個物件組合成一個檢視,系統中最常見的無非是兩種——列表、樹,以及兩者的結合體——TreeGrid

  列表是比較簡單的結構,按實際的需求應用,不會產生太多誤解。而樹結構卻有值得討論之處。最簡單的樹節點實現的程式碼如下:程式碼很簡單,只有節點的名稱,和對程式碼下級節點的管理。

  

  如果我們應對的業務很簡單,例如類似於windows系統的資料夾樹,即每個節點的型別都一樣,每個節點的功能也都一樣,葉子節點和摘要節點在功能上沒有區別。這種情況下,可以用以上程式碼輕鬆應對。

  但是如果遇到以下情況呢,如下圖:

  

  這也是個樹結構,但是每個節點型別都不一樣,形式的功能也不一樣,“個人”是個葉子節點,不能再新增下級節點。這種情況下,再用以上那段程式碼就會出現許多問題,如多個功能集中在一個程式碼檔案中,多人維護一段程式碼等。

   如何解決這一問題,組合模式給予我們靈感。

  

  根據以上類圖,可以看出組合模式解決這一問題的思路是:將樹結構中的節點的統一功能抽象出來,不同型別的節點,用不同的子類去實現。類圖中只有兩個子類,我們可以根據自己的實際情況來派生多個子類。

  這樣解釋想必大部分人都能理解該模式的設計思路,不必再用程式碼挨著表達了。關鍵在於理解如何分析問題,如何抽象,如何隔離,如何解耦,最終就是如何設計。

  這樣設計符合開放封閉原則、職責單一原則,對於客戶端也符合依賴倒置原則。

7.6   情況5:分離多層繼承

  在物件組合過程中,難免會出現繼承的情況,甚至會出現多層繼承。根據設計原則——少繼承、多聚合。因此不建議我們使用多層繼承。而是儘量把這種多層繼承的關係,變成聚合的關係。

  在一個多層繼承結構中,如果底層節點可以抽象出相同的功能,即可變為聚合關係。如:

  

  如上圖,子類可以提取出一個抽象。變成這樣的設計:

  

  這樣就把多繼承變成了聚合。

        

  可以總結歸納以下這種情況。我們把左側“傳送訊息”及其子類叫做“抽象”,右側的“傳送方式”及其子類叫做“實現”。那麼我們現在做的就是從“實現”和“抽象”的繼承關係,變成了兩者的聚合關係。

  這就是——橋接模式。以下是類圖:

  

  他應對的場景是抽象有多樣性,實現也有多樣性。抽象的抽象只依賴於實現的抽象。從而解耦抽象和實現的關聯。

7.7   情況6:封裝組合,供外部使用

  當一個組合封裝完成後,要提供統一的介面供外部客戶端使用。而不是讓客戶端在組合內部任意的呼叫。

  這就是外觀模式。很好理解,也經常用到,可能只是不知道這個名字而已。它像一個包袱一樣包起來組合,只留規定的介面。

  

  外觀模式簡單易懂,不需要類圖和程式碼過多解釋。

7.8   總結:

  (注:未包括“Flyweight享元模式”。將在後續版本更新中加入。)

  其實以上這幾種情況,就是結構性的設計模式對應的問題,每種情況對應一種設計模式。結合自己或多或少的開發經驗,仔細考慮分析這幾種情況,肯定每種情況都是你在編碼中遇到的,也是一個物件組合很可能需要的。

  遇到這些問題時,你當時是怎麼解決的?不一定非得按照設計模式上的解決方式。但是要已定符合設計原則。設計模式只是一個“術”,提供一個解決思路或者靈感,而設計原則、設計思想才是“道”。

 

  特別說明:

  上、中兩篇其實是我已經整理好的資料,直接複製貼上到這裡的(從word中往這裡面貼圖挺麻煩),因此更新比較快。這些資料是我從5.12開始,每天都花一小時時間整理的,算來也兩週了。下一篇資料現在剛剛開始整理,估計不會很快就更新上。因為我需要把剩下的設計模式挨個分析,然後串起來,不容易。有需要的朋友得耐心等兩天,但是我肯定會在發出來。

  補充: 種思路去理解設計模式(下)   (2014-06-04)

 

相關文章