設計六大原則總結

一、單一職責原則(SRP)

定義:就一個類而言,應該僅有一個引發它變化的緣由
爲何須要單一職責呢?若是一個類承擔的職責過多,就等於把這些職責耦合在一塊兒了,一個職責的變化可能會引發其它職責的變化,當變化發生時,設計會遭到意想不到的變化。
咱們看看下面簡單的類圖,UserDiscount類具備兩個方法,一個是獲取等級類型,一個是計算折扣價格。

有兩個不一樣的類在使用UserDiscount,Order須要獲取用戶等級和計算價格;User只須要獲取用戶等級,但不須要計算價格,這個設計違反類SRP,若是其中一個使用類的改變致使UserDiscount改變,這樣會致使其它使用類也須要變動、測試、部署等問題。咱們須要拆分兩個職責類,以下圖:

可是,若是類的變化老是致使這兩個職責的同時變化,那麼就沒必要分離它們,實際上,分離它們可能會致使複雜性增長。或者說,變化的軸線僅當變化實際發生時才具備真正意義。若是沒有徵兆,那麼去應用SRP或者其它原則都是不可取的。
結論:SRP是最簡單的職責之一,可是也比較難正確運用的職責,在開發中,會天然地把職責結合在一塊兒,畢竟有些職責須要耦合在一塊兒的,而難以拆分並增長複雜性。ide

二、開放封閉原則(OCP)

定義:軟件實體(類、模塊、函數等等)應該是能夠擴展的,可是不能夠修改的函數

  • 對於擴展是開放的:模塊行爲是能夠擴展的,當應用需求改變時,咱們能夠對模塊進行擴展,使其知足那些改變的行爲。
  • 對於修改是封閉的:對模塊擴展時,沒必要改動模塊的源代碼

下面來看個播放MP3的例子,MP3和Player都是具體類,MP3直接使用Player播放音樂,可是若是須要播放音頻,那麼就須要從新修改Player而致使MP3也須要修改。

下面咱們修改下例子而遵循OCP原則測試


這個設計中,IPlayer是一個接口,MP3和Video繼承該接口,從此想增長其它類型的播放只須要繼承IPlayer就行,無需修改MP3或Video類。
但實際開放中,不管模塊多麼封閉,都會存在一些沒法對之封閉的現象,那就須要有策略的去對待這個問題,模塊應該對哪一種變化封閉而作出選型,必須先猜想最有可能發生變化的狀況,而後構造出抽象來隔離。
結論:遵循OOP能夠帶來靈活性、可重用性、以及可維護性。然而,對於應用程序中每一個部分都肆意的進行抽象一樣是不行的,這樣屬於不成熟抽象,咱們只須要把頻繁變化的部分進行抽象就行。設計

三、Liskov替換原則(LSP)

定義:子類型必須可以替換掉它們的基類型
舉個例子,函數a使用的參數是基類B,可是C類繼承基類B,但把C作爲參數傳給了函數a而致使其發生錯誤,這樣就是違反了LSP原則。主要體如今下面四個方面:對象

  • 子類必須實現父類的抽象方法,但不得重寫(覆蓋)父類的非抽象(已實現)方法。
  • 子類中能夠增長本身特有的方法。
  • 當子類覆蓋或實現父類的方法時,方法的前置條件(即方法的形參)要比父類方法的輸入參數更寬鬆。
  • 當子類的方法實現父類的抽象方法時,方法的後置條件(即方法的返回值)要比父類更嚴格。

下面來看下簡單類圖,違反來SRP原則,定義了一個Rectangle和一個繼承自Rectangle的Square,看着是很是符合邏輯的,可是咱們從新設置Rectangle的寬度,會致使Square的寬度也會變更,致使Square出錯。

改變一下不符合SRP,咱們再定義一個他們共同的父類Graphics,而後讓Rectangle和Square都繼承自這個父類。在基類Graphics類中沒有賦值方法,所以重設寬高不可能適用於Graphics類型,而只能適用於不一樣的具體子類Rectangle和Aquare,所以里氏替換原則不可能被破壞。而且下面的設計也符合OCP原則。

結論:使用LSP,使得程序具備更多的可維護性、可重用性以及健壯性。而LSP是使OCP成爲可能的主要原則之一,子類型的可替換性才使得基類類型的模塊在無需修改的狀況下能夠擴展。繼承

四、依賴倒置原則(DIP)

定義:高層模塊不該該依賴於低層模塊,兩者應該依賴於抽象;抽象不該該依賴於細節,細節應該依賴於抽象。
下面來看下簡單例子,用戶有多個用戶等級類UseOrdinary和UserDiamond,而UserTypeService使用等級類進行相關的邏輯處理,從此若是加強其它用戶等級,就須要修改UserTypeService,這樣違反類DIP,高層策略沒有和低層實現分離,抽象沒有和具體細節分離,沒有這種分離,高層策略就自動地依賴於低層策略,抽象就自動地依賴於具體細節。

咱們變動下具體的實現方式,抽象出UseType接口,UseOrdinary和UserDiamond繼承該接口,而UserTypeService使用了UseType,無論從此增長什麼用戶等級都無需修改UserTypeService,並對於具體的實現類咱們是無論的,只要接口的行爲不發生變化,增長新的用戶等級後,上層服務不用作任何的修改。這樣設計下降了層與層之間的耦合,能很好地適應需求的變化,大大提升了代碼的可維護性。

結論:設置倒置的依賴關係結構,使得細節和策略都依賴於抽象,屬於面向對象設計;若是依賴關係不倒置,屬於過程化設計。接口

五、接口隔離原則(ISP)

定義:不該該強迫繼承類依賴於它們不使用的接口方法,類間的依賴關係應該創建在最小的接口上
使用者依賴了那些它們不使用的方法,就面臨着這些未使用的方法改變而帶來的變動,無心中致使了它們之間的耦合,下面來看下簡單示例,MatchingHandler是一個匹配接口,包含匹配系統ID(handleSystemId)和處理聯賽ID(detectLeagueId),MatchMatching和LeagueMatching繼承了該接口,但MatchMatching不須要處理處理聯賽ID,也繼承了該方法,這樣方法改變而帶來的變動。

咱們在來看下變動後的簡單類圖,新增了LeagueMatchingHandler(detectLeagueId),LeagueMatching繼承了該接口,detectLeagueId方法的變動不會致使MatchMatching也須要變動,只會影響到LeagueMatching。

結論:胖類是這個類過於臃腫,可能會致使使用者產生不正常的耦合關係,該類的修改也會致使使用者的修改。使用接口分解,使用者只須要使用特定的接口,並解除了和胖類的耦合關係。ip

六、迪米特原則(LOD)

定義:類之間儘量少與其餘實體發生相互做用
在開發中,咱們常常提到高內聚低耦合,使各個模塊之間的耦合儘可能的低,才能提升代碼的複用率,耦合的方式不少,依賴、關聯、組合、聚合等。其中,咱們稱出現成員變量、方法參數、方法返回值中的類爲低耦合,而出如今局部變量中的類則高耦合。也就是說,陌生的類最好不要做爲局部變量的形式出如今類的內部。下面咱們來看下例子,定義了Match,Team和Player,Match都引用了Team和Player,Team又引用了Player,這樣違反了LOD,致使了Match跟Player耦合增長。

下面咱們來變動下引用,Match只須要引用了Team就行,無需在引用Palyer,由於Team已經引用了Player。這樣Match能夠打印出相關選手了。

結論:LOD的初衷是下降類之間的耦合,因爲每一個類都減小了沒必要要的依賴,所以的確能夠下降耦合關係,但這樣必須會產生一箇中介類,由這個中介類來處理類之間的通訊,過多的中介類會致使系統複雜度增大而難以維護。設計的時候須要權衡,保持結構清晰和高內聚低耦合開發

相關文章
相關標籤/搜索