設計模式 - 開篇

   什麼是設計模式(Design Pattern)?

  在我個人看來,模式一般是指內容會有邊界(Border)或有比較固定內容(Fixed Content)的指導性東西,類似於路走多了就進而形成了路,這個路是有明顯邊界的和指導性的,所以個人理解的設計模式是特定問題的常用指導解決方案。

  設計模式是高層次的解決方案,它要求個人在碰到問題時,不要過多關注問題的細節,將問題泛化和抽象化剝離出問題的核心,進而匹配看是否符合衆多設計模式的使用場景而選用。所以設計模式描述的是:在各種情況下要選擇什麼樣的方案來解決問題。

  項目中合理地運用設計模式可以完美地解決很多問題,每種模式都在描述一個在我們周圍不斷重複發生的問題,以及該問題的核心解決方案。設計模式可以貫穿在開發乃至重構的過程中,使代碼處於優雅且複用的狀態以增強軟件設計的適應變化能力。這也是設計模式的目的:提高代碼可重用性和可靠性,並使代碼條理清晰、易於理解、易於維護。

  有哪些設計模式可供使用?

  根據GOF《設計模式》著作中所說,設計模式可以分成三組:創建型(Creational),結構型(Structural),行爲型(Behavioral),共23種。

       

  什麼情況使用設計模式?

  • 設計模式是複雜的,在引用前要衡量是否有必要給項目引入額外的複雜性。這要求使用者衡量實現某種模式所需的時間與該模式能夠帶來的效益。

  • 不要在不瞭解設計模式的情況下使用他們。

  • 使用者需有強大的概括能力,問題抽象出來都錯了,談何使用?

  設計原則

  爲什麼要提倡設計模式呢?上面提到了設計模式的目的是爲了代碼複用,增加可維護性。那麼怎麼才能讓開發人員輕鬆寫出可讀性和可維護性高的程序呢?

  Martin(Uncle Bob)提出了五項原則,這五個原則被稱爲S.O.L.I.D原則(首字母縮寫):

  

  SRP - 單一職責  

  作爲開發者,想必大家都有這樣的經驗,一個方法參雜越多的交叉業務,它的定義就越不明確,複用性就越低。小至方法,大至模塊,承擔的職責越多,就等於把這些職責耦合在一起,它被複用的可能性就越小。當一個類具有了多項職責,它被更改的可能性也會增加,當其中一個職責發生變化時,可能會影響其他職責的運作,而每一次由於職責變化發生的改動,也會使得bug產生的風險增加。

  所以SRP強調的是:引起類變化的因素永遠不要多於一個。這意味着在設計需要的類時,需要考慮使得每個類被設計出來都只有一個目的(或主要目的)。但這並不意味着每個類只能有一個方法,應該是該類中所有的方法都要圍繞着該類所描述的主要功能。至於那些有多個職責的類,應該被重新封裝成新的類。

  OCP - 開閉原則

  該原則強調的是:一個軟件實體(指的類、函數、模塊等)應該對擴展開放,對修改關閉。即每次發生變化時,要通過添加新的代碼來增強現有類型的行爲,而不是修改原有的代碼。

  爲什麼要這樣呢?我們都有這樣的經歷,在接手已經在正式上線的項目時,當需要面對新的需求時,我們首要的任務應該是儘量保證系統的設計框架是穩定的。對於一個功能,一般不會因爲新功能的原因而去改變之前已經穩定的功能,因爲如果你改變它,很可能你的改變會引發系統的崩潰。

  OCP提倡的是你需要一些額外功能,你應該擴展這個類而不是修改它。使用這種方式,現有系統不會看到由於新變化的所帶來的影響。同時,你只需要測試新創建的類。與SRP一樣,該原理通過儘可能減少對現有代碼的更改來降低引入新錯誤的風險。

  符合開閉原則的最好方式是提供一個固有的接口(或抽象類),爲系統提供一個相對穩定的抽象層,然後讓所有可能發生變化的類實現該接口,讓固定的接口與相關對象進行交互,這樣如果需要修改系統的行爲,只需在抽象層進行新增業務方法,然後增加新的具體類來實現新的業務功能即可。

  LSP - 里氏替換原則

  里氏替換原則(LSP)聲明:所有引用基類的地方必須能使用其子類的對象。也就是說任何基類可以被調用的地方,子類也一定可以被調用。

  在軟件開發過程中,只有當子類替換掉父類後,此時軟件的功能不受影響時,父類才能真正地被複用,而子類也可以在父類的基礎上添加新的行爲。舉個例子:我喜歡運動,那能推斷出我一定喜歡跑步,因爲跑步是運動的一種;但是從我喜歡跑步卻不能推斷我喜歡運動,因爲我並不喜歡蹦極,雖然它也是運動的一種。

  ISP - 接口分離原則

  接口分離原則:使用多個專門的接口比使用單一的總接口要好也就是說不要讓一個單一的接口承擔過多的職責,而應把每個職責分離到多個專門的接口中,進行接口分離。

  我們應該都有這樣的經歷,在一個大的業務類型接口中,定義了非常多的方法,當業務需要在該接口添加新方法時,所有實現該接口的類都要去實現該方法,這樣就會導致即使我負責的業務跟你新加的方法沒有太大關係,我也得去實現你的方法,並且由於需要實現新的方法,導致客戶端會暴露這個方法。

  根據接口分離原則,推薦的實現的方式應該是:把大的接口拆分,讓大類實現多個更小的接口,根據用途對功能進行分組。依賴關係與那些相關聯用於鬆耦合,增加健壯性,靈活性以及可複用性。我們需要注意控制接口的粒度,接口不能太小,如果太小會導致系統中接口氾濫,不利於維護;接口也不能太大,太大的接口將違背接口分離原則,靈活性較差,使用起來很不方便。

  那這個度是如何控制的呢?這裏有一個準則是,基於客戶端需要的用途對功能進行分組,僅僅提供客戶端需要的行爲,不需要的行爲則隱藏起來,應當爲客戶端提供儘可能小的單獨的接口,而不要提供大的總接口。

  我們能看出這是SRP的延伸,所以重構的過程中,可以結合接口隔離原則和單一職責原則進行代碼重構。

  DIP - 依賴倒置原則

  先說一下這個依賴關係是什麼。

  在實際的開發過程中,我們很多人總是傾向於創建一些高層模塊依賴於低層模塊的開發策略,因爲是低層次提供了對外的接口,高層次只能依賴於低層次所提供的接口,所以這個依賴關係應該是高層依賴於底層。
  如何理解高層次和低層次?通俗點說:當A需要用到B時,那A就是高層次,B就是低層次。很明顯,如果設計了這些高層模塊依賴於低層模塊,那麼對低層模塊的改動就會直接影響到高層模塊,從而迫使它們需要作出改動,高層將沒有任何的自主性。

  如何去除這樣的關係?依賴倒置原則提倡的是:高層模塊不應該依賴於低層模塊,至此我們應該知道了這個倒置的含義,應該是解除高層和底層的直接耦合關係。很明顯,去除這層關係是對的,但如何實現呢?

  依賴倒置的原則是:依賴抽象,不依賴具體實現,也就是高層和底層的耦合關係通過抽象(接口)來實現。這也是我們提倡的面向接口編程,與之相關的概念是DI(依賴注入)和IoC(控制反轉)。

  該原則規定了在類之間存在依賴關係的情況下,應使用抽象(如接口)來構建它們的耦合關係,而不是直接引用類。 這減少了由低層次模塊的變化而導致高層模塊的改動。

 

   後記  

  通常我們接手一個依賴關係很糟糕的項目要進行重構時,你會發現裏面代碼可能會混亂,脆弱且難以重用。在這過程中我們勢必要改變現有功能或添加新的功能,而這樣的代碼會讓我們維護的過程變得舉步維艱。脆弱的代碼很容易造成bug的產生,常見的情況是你一個區域的代碼發生變化時候,造成你其他模塊出現bug。如果你遵從SOLID原則,那麼你可以編寫出更靈活更健壯的代碼,並且具有更高的重用性。  

  設計模式就是實現了以上這些原則,從而達到了代碼複用、增加可維護性的目的。這些設計原則在編碼過程是非常有用的,它給予指導性的思想去編寫高質量的可重用代碼, 可以顯著的提升我們軟件的可維護性。

  通常大部分的成熟重量級框架中,設計模式是必不可少的,類似於微軟的MVC,EntityFramework框架中,穿插着大量的設計模式,如果你熟悉了這些設計模式,毫無疑問,這將會祝你迅速掌握框架的結構。

  後面我們將會針對上面提到的3人組設計模式進行實例講解,不一定所有的模式都會講到,因爲在實際應用中,也不可能所有的設計模式都會用到,而且個人由於工作經歷也沒完全遇到和使用所有的設計模式:)

  

   讓我知道如果你有更好的想法或建議:)

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