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

1 前言

  看過許多關於設計模式的博客,也讀過關於設計模式的書。幾乎所有的介紹的開頭,直接就引入了“設計模式”或者“某某模式”。設計模式到底是因什麼而來?這是一個很重要的問題。孫悟空從石頭縫裏蹦出來,《西遊記》還介紹了這個石頭的來歷呢。

  要想了解一個東西,至少有“3W”——what、why、how——是什麼、爲什麼、怎麼用。看現在大部分的文章或者書籍,重點介紹的還是“what”,這就有點類似於:爲了用設計模式用設計模式。在這種思想的教導下去了解設計模式,學不會也很正常。

     另外,介紹一個東西的用處時,不要弄一些小貓小狗、肯德基、打籃球、追MM這話總例子。這就像用小學課本的兒童故事來給你講解一個人生道理一樣,你聽着明白,但是真能理解嗎?

 

2  概述

  記得之前看過一篇博客,具體內容現在都忘記了。記得上面有句話大體是什麼說的:所謂的設計模式,我們日常工作中經常用,只是我們沒有想過像GoF一樣,把這些日常用到的模式總結歸納,形成結構化的理論。

  可見,設計模式不真正是GoF提出的概念,而是他們作爲一個有心人,把人們日常工作中遇到的設計問題,全面的總結,才形成了之後的“23種設計模式”。

   首先,設計模式解決的肯定是系統設計問題,而且會用到面向對象來解決的。所以,本書開頭先說設計原則和麪向對象。面向對象基礎知識,大部分人應該都瞭解;至於設計原則,不瞭解的人必須要先了解。

  其次,我們將模擬一個簡單的對象聲明週期過程,從對象的創建、封裝、組合、執行和操作,一步一步走來,會遇到許多情況和問題。針對問題,我們將通過思考,利用面向對象和設計原則,解決這個問題。而解決這個問題的方法,便是一種設計模式。

  最後,23種設計模式不是一盤散沙,是有關係的。就是對象的生命週期一步一步的將各個設計模式串聯在了一起。對象的生命週期中,會一步一步的遇到總共23種設計問題,所以纔會有23種設計模式。

3  設計原則

  設計模式解決的肯定是系統設計的問題,所以首先從“設計”說起。

  設計所要解決的主要問題,是如何高效率、高質量、低風險的應對各種各類變化,例如需求變更、軟件升級等。設計的方式主要是提取抽象、隔離變化,有5大設計原則——“SOLID”,具體體現了這個思路。

  • S - 單一職責原則:

  一個類只能有一個讓它變化的原因。即,將不同的功能隔離開來,不要都混合到一個類中。

  • O - 開放封閉原則:

  對擴展開放,對修改封閉。即,如果遇到需求變化,要通過添加新的類來實現,而不是修改現有的代碼。這一點也符合單一職責原則。

  • L - Liskov原則:

  子類可以完全覆蓋父類。

  • I - 接口隔離原則:

  每個接口都實現單一的功能。添加新功能時,要增加一個新接口,而不是修改已有的接口,禁止出現“胖接口”。符合單一職責原則和開放封閉原則。

  • D – 依賴倒置原則:

  具體依賴於抽象,而非抽象依賴與具體。即,要把不同子類的相同功能抽象出來,依賴與這個抽象,而不是依賴於具體的子類。

 

  總結這些設計原則可知,設計最終關注的還是“抽象”和“隔離”。面向對象的封裝、繼承和多態,還有每個設計模式,分析它們都離不開這兩個詞。

4  面向對象基礎

  繼承、封裝、多態

  接口、抽象類

5  一個對象的生命週期

  一個對象在系統中的生命週期可以概括爲以下幾點:

  • 對象創建:

  想到對象創建,最多的就是通過new一個類型來創建對象。但也會有許多特殊的情況,例如對象創建過程很複雜,如何解耦?等等。

  • 對象組合、包裝:

  一個對象創建後,可能需要對其就行包裝或者封裝,還可能由多個對象組成一個組合結構。在這過程中,也會遇到各種問題。

  • 對象操作:

  對象創建了,也組合、包裝完畢,然後就需要執行對象的各種操作,這是對象真正起作用的階段。對象的操作情況衆多,問題也很多。

  • 對象消亡:

  直到最後對象消亡,在C#中將被GC回收。

 

  以上簡單介紹這個過程,其中的具體描述以及遇到的情況和問題,會在下文中詳細講解

6   創建一個對象

6.1   過程描述

一般對象的創建可以new一個類型,相信系統中絕大部分的對象創建也是這麼做的。但是如果遇到以下情況,直接用new一個類型,會遇到各種各樣的問題。

6.2   情況1:拷貝創建

  系統中肯定會遇到這種情況,新建對象時,要用到一個現有對象的許多屬性、方法等。這時候再通過new一個新的空對象,還需要把這些屬性、方法都賦值到新對象中,帶來不必要的工作量。

  提出這個問題,我們會想到克隆,也可能已經在系統中用到了克隆。其實這個就是一個比較簡單的設計模式——原型模式。我們把這個“克隆”動作抽象到一個接口中,需要克隆的類型,實現這個接口即可。

        

         C#已經在FCL(Framework Class Library)中定義了一個接口——IColoneable,因此不需要我們在自己定義該接口,只需要在用到的地方實現即可。IColoneable接口只定義了一個Colone方法:

        

  例如FCL中的String類,實現了IColoneable接口,並實現了接口方法Colone()。

        

 

6.3  情況2:限制單一對象

  如果一個對象定義的屬性和方法,可供系統的所有模塊使用,例如系統的一些配置項。此時無需再去創建多個對象。也不允許用戶創建多個對象,因爲一旦修改,只修改這一個對象,系統的所有模塊都將生效。

  我們把這個只能實例化一次的對象叫做“單例”,這種模式叫做單例模式

  其實系統中的靜態類,就是這種“單例”的設計思想。例如FCL中的Console類,它是一個靜態類,它給系統提供的就是一個“單例”類。

   

  只不過Console是一個類型,而不是對象,缺點就是無法作爲對象賦值和傳遞。如果系統中需要的“單例”就是一些功能,涉及不到對象的賦值和傳遞,完全可以用靜態類實現,沒必要非得用單例對象。

  對象的單例模式,關鍵在於限制類型的構造函數,不讓使用者隨意new一個新對象,且看代碼:

  

  重點:將構造函數設置爲private,只能內部調用;用一個靜態字段來存儲對象。

  可見,無論單例是類型還是對象,都需要通過“靜態”來實現。

6.4   情況3:複雜對象

  創建一個新對象時,一般需要初始化對象的一些屬性。簡單的初始化可以用通過構造函數和直接賦值來完成。

  

  但是如果一個對象的屬性過多,業務邏輯很複雜,就會導致複雜的創建過程。這種情況下,用構造函數是不好解決的。如果用直接賦值,就會導致大量的if…else…或者switch…case...的條件判斷。這樣的代碼將給系統的維護和擴展帶來不便,而且如果不改變設計,會隨着維護和擴展,會出現更多的條件判斷。隨着代碼量的增加,維護難度更大。如果再是多人同時維護,那就麻煩了。

  

  顯然,這樣的代碼不是我們所期望的。設計上也不符合單一指責原則、開放封閉原則。所以,對於一個複雜對象的創建過程,我們將考慮重構。

  我們把對象創建的過程抽象出來,做成一個框架,然後派生不同的子類,來實現不同的配置。將複雜對象的構建與其表示分離,這就是建造者模式

  

  上圖中,我們最終要創建的是Product類型的對象,Product是個複雜對象。如果直接new一個對象,再賦值,會導致大量條件判斷。

  所以,我們將對象創建過程抽象到一個Builder抽象類中,然後用不同的子類去實現具體的對象創建。這樣的設計相比之前大量的if-else-代碼,優勢是非常明顯的,並且符合單一職責原則和開放封閉原則。應對需求變更、新功能增加、多人協同開發都是有好處的。

6.5   情況4:功能相同的對象

  最經典的就是數據操作。創建一個用於SQL server的SQLDBHelper類,又創建了一個用於Oracle的OracleDBHelper類,這兩個類所實現的功能是完全一樣的,都是增刪改查等。如果這兩個類是孤立的,那系統數據庫切換時候,將導致SQLDBHelper和OracleDBHelper兩個類之間的切換,而且改動工作量隨着系統複雜度增加。

  

  而且如果增加一個數據庫類型,也會導致系統代碼的大量修改。

  

  這個問題的根本是違反了依賴倒置原則。客戶端應該依賴於抽象,而不是具體實現。我們應該把數據操作的功能抽象出來,然後通過派生子類來實現具體。

  

  這樣設置之後,我們創建對象的代碼就會變成:

  

  面對不同的數據庫,我們需要判斷並創建不同的實現類。

  

  可以把這段代碼封裝成一個方法,這就是一個簡單的“工廠”。所謂工廠,就是封裝一個對象創建過程,對於一種抽象,到底由哪個具體實現,由工廠決定。

  

  這是一個簡單工廠模式。另外,工廠方法模式抽象工廠模式也都是在這個基礎上再去抽象、分離,而出來的。

6.6   總結

  對象創建並不是new一個類型這麼簡單,以上四種情況在日常開發過程中應用也都比較常見。

  上面通過對象創建過程的種種情況,隨之介紹出了:原型模式、代理模式、建造者模式、工廠模式。雖然現在還不能完全瞭解這些模式的細節,但是至少明白了這些模式應對什麼問題,有了明確的定位。而這纔是最關鍵的,有了定位,有了高層次的理解,再看細節就變得容易多了。

 

後文繼續,敬請期待!

---------------------------------------------------------------------------------------------

7. 多對象組成結構

7.1 過程描述

7.2 情況1:借用外部接口

7.3 情況2:給對象增加新功能

7.4 情況3:封裝功能

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

7.6 情況5:分離多層繼承

7.7 情況6:封裝組合,供客戶使用

7.8 總結

 

8. 對象行爲與操作對象

…… 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章