設計模式之禅觀後感
目錄
-
- 設計模式之禅觀後感
- 單一職責原則(Single Responsibility Principle,SRP)
-
- 最佳實踐
- 裡氏替換原則(Liskov Substitution Principle,LSP)
-
- 最佳實踐
- 依賴倒置原則(Dependence Inversion Principle,DIP)
-
- 最佳實踐
- 接口隔離原則(Interface Segregation Principle,ISP)
-
- 最佳實踐
- 迪米特法則的定義(Law of Demeter,LoD)
- 最少知識原則(Least Knowledge Principle,LKP)
-
- 最佳實踐
- 開閉原則(Open Closed Principle,OCP)
-
- 最佳實踐
- 總結
單一職責原則(Single Responsibility Principle,SRP)
單一職責原則的英文名稱是Single Responsibility Principle,簡稱是SRP。SRP的原話解釋是:
There should never be more than one reason for a class to change.
單一職責原則的定義是:應該有且僅有一個原因引起類的變更。
單一職責的優點:
● 類的複雜性降低,實作什麼職責都有清晰明确的定義;
● 可讀性提高,複雜性降低,那當然可讀性提高了;
● 可維護性提高,可讀性提高,那當然更容易維護了;
● 變更引起的風險降低,變更是必不可少的,如果接口的單一職責做得好,一個接口修改隻對相應的實作類有影響,對其他的接口無影響,這對系統的擴充性、維護性都有非常大的幫助。
單一職責原則很難在項目中得到展現,非常難,為什麼?在國内,技術人員的地位和話語權都比較低,是以在項目中需要考慮環境,考慮工作量,考慮人員的技術水準,考慮硬體的資源情況,等等,最終妥協的結果是經常違背單一職責原則。而且,我們中華文明就有很多屬于混合型的産物,比如筷子,我們可以把筷子當做刀來使用,分割食物;還可以當叉使用,把食物從盤子中移動到口中。而在西方的文化中,刀就是刀,叉就是叉,你去吃西餐的時候這兩樣肯定都是有的,刀就是切割食物,叉就是固定食物或者移動食物,分工很明晰。這種文化的差異很難一步改造過來,但是我相信随着技術的深入,單一職責原則必然會深入到項目的設計中,而且這個原則是那麼的簡單,簡單得不需要我們更加深入地思考,單從字面上大家都應該知道是什麼意思,單一職責嘛!
最佳實踐
對于接口,我們在設計的時候一定要做到單一,但是對于實作類就需要多方面考慮了。生搬硬套單一職責原則會引起類的劇增,給維護帶來非常多的麻煩,而且過分細分類的職責也會人為地增加系統的複雜性。本來一個類可以實作的行為硬要拆成兩個類,然後再使用聚合或組合的方式耦合在一起,人為制造了系統的複雜性。是以原則是死的,人是活的,這句話很有道理。單一職責原則提出了一個編寫程式的标準,用“職責”或“變化原因”來衡量接口或類設計得是否優良,但是“職責”和“變化原因”都是不可度量的,因項目而異,因環境而異。
裡氏替換原則(Liskov Substitution Principle,LSP)
在面向對象的語言中,繼承是必不可少的、非常優秀的語言機制,它有如下優點:
- 代碼共享,減少建立類的工作量,每個子類都擁有父類的方法和屬性;
- 提高代碼的重用性;
- 子類可以形似父類,但又異于父類,“龍生龍,鳳生鳳,老鼠生來會打洞”是說子擁有父的“種”,“世界上沒有兩片完全相同的葉子”是指明子與父的不同;
- 提高代碼的可擴充性,實作父類的方法就可以“為所欲為”了,君不見很多開源架構的擴充接口都是通過繼承父類來完成的;
- 提高産品或項目的開放性。
繼承的缺點如下:
- 繼承是侵入性的。隻要繼承,就必須擁有父類的所有屬性和方法;
- 降低代碼的靈活性。子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些限制;
-
增強了耦合性。當父類的常量、變量和方法被修改時,需要考慮子類的修改,而且在缺乏規範的環境下,這種修改可能帶來非常糟糕的結果——大段的代碼需要重構。
Java使用extends關鍵字來實作繼承,它采用了單一繼承的規則。從整體上來看,利大于弊,怎麼才能讓“利”的因素發揮最大的作用,同時減少“弊”帶來的麻煩呢?
解決方案是引入裡氏替換原則,什麼是裡氏替換原則呢?它有兩種定義:
- 第一種定義,也是最正宗的定義:
If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T.
如果對每一個類型為S的對象o1,都有類型為T的對象o2,使得以T定義的所有程式P在所有的對象o1都代換成o2時,程式P的行為沒有發生變化,那麼類型S是類型T的子類型。
2. 第二種定義:
Functions that use pointers or references to base classes must be able to use objects of derived classes without knowing it.
所有引用基類的地方必須能透明地使用其子類的對象。第二個定義是最清晰明确的,通俗點講,隻要父類能出現的地方子類就可以出現,而且替換為子類也不會産生任何錯誤或異常,使用者可能根本就不需要知道是父類還是子類。但是,反過來就不行了,有子類出現的地方,父類未必就能适應。
裡氏替換原則為良好的繼承定義了一個規範,一句簡單的定義包含了4層含義。
- 子類必須完全實作父類的方法
- 子類可以有自己的個性
- 覆寫或實作父類的方法時輸入參數可以被放大
- 覆寫或實作父類的方法時輸出結果可以被縮小
最佳實踐
在項目中,采用裡氏替換原則時,盡量避免子類的“個性”,一旦子類有“個性”,這個子類和父類之間的關系就很難調和了,把子類當做父類使用,子類的“個性”被抹殺——委屈了點;把子類單獨作為一個業務來使用,則會讓代碼間的耦合關系變得撲朔迷離——缺乏類替換的标準。
依賴倒置原則(Dependence Inversion Principle,DIP)
依賴倒置原則的原始定義是:
High level modules should not depend upon low level modules.Both should depend upon abstractions.Abstractions should not depend upon details.Details should depend upon abstractions.
翻譯過來,包含三層含義:
- 高層子產品不應該依賴低層子產品,兩者都應該依賴其抽象;
- 抽象不應該依賴細節;
-
細節應該依賴抽象。
在Java語言中,抽象就是指接口或抽象類,兩者都是不能直接被執行個體化的;細節就是實作類。
依賴倒置原則在Java語言中的表現就是:
- 子產品間的依賴通過抽象發生,實作類之間不發生直接的依賴關系,其依賴關系是通過接口或抽象類産生的;
- 接口或抽象類不依賴于實作類;
- 實作類依賴接口或抽象類。
- 更加精簡的定義就是“面向接口程式設計”
依賴的三種寫法
- 構造函數傳遞依賴對象
- Setter方法傳遞依賴對象
- 接口聲明依賴對象
最佳實踐
- 每個類盡量都有接口或抽象類,或者抽象類和接口兩者都具備
- 變量的表面類型盡量是接口或者是抽象類
- 任何類都不應該從具體類派生
- 盡量不要覆寫基類的方法
- 結合裡氏替換原則使用
接口隔離原則(Interface Segregation Principle,ISP)
接口隔離原則是對接口進行規範限制,其包含以下4層含義:
-
接口要盡量小
此處舉個栗子, 挂電話有兩種方 式:一種是正常的電話挂斷,一種是電話異常挂機,比如突然沒電了,通信當然就斷了。這兩種方式的處理應該是不同的,為什麼呢?正常挂電話,對方接受到挂機信号,計費系統也 就停止計費了,那手機沒電了這種方式就不同了,它是信号丢失了,中繼伺服器檢查到了, 然後通知計費系統停止計費,否則你的費用不是要瘋狂地增長了嗎?
思考到這裡,我們是不是就要動手把接口拆封成兩個,一個接口是負責連接配接,一個接口是負責挂電話?是要這樣做嗎?且慢,讓我們再思考一下,如果拆分了,那就不符合單一職責原則了,因為從業務邏輯上來講,通信的建立和關閉已經是最小的業務機關了,再細分下去就是對業務或是協定(其他業務邏輯)的拆分了。想想看,一個電話要關心協定,要考慮中繼伺服器,等等,這個電話還怎麼設計得出來呢?從業務層次來看,這樣的設計就是一個失敗的設計。一個原則要拆,一個原則又不要拆,那該怎麼辦? 好辦,根據接口隔離原則拆分接口時,首先必須滿足單一職責原則。
-
接口要高内聚
什麼是高内聚?高内聚就是提高接口、類、子產品的處理能力,減少對外的互動。
-
定制服務
定制服務就是單獨為一個個體提供優良 的服務。我們在做系統設計時也需要考慮對系統之間或子產品之間的接口采用定制服務。采用 定制服務就必然有一個要求:隻提供通路者需要的方法
-
接口設計是有限度的
接口的設計粒度越小,系統越靈活,這是不争的事實。但是,靈活的同時也帶來了結構 的複雜化,開發難度增加,可維護性降低,這不是一個項目或産品所期望看到的,是以接口 設計一定要注意适度,這個“度”如何來判斷呢?根據經驗和常識判斷,沒有一個固化或可測 量的标準。
最佳實踐
接口隔離原則是對接口的定義,同時也是對類的定義,接口和類盡量使用原子接口或原 子類來組裝。但是,這個原子該怎麼劃分是設計模式中的一大難題,在實踐中可以根據以下 幾個規則來衡量:
- 一個接口隻服務于一個子子產品或業務邏輯;
- 通過業務邏輯壓縮接口中的public方法,接口時常去回顧
- 已經被污染了的接口,盡量去修改,若變更的風險較大,則采用擴充卡模式進行轉化 處理;
- 了解環境,拒絕盲從。
迪米特法則的定義(Law of Demeter,LoD)
最少知識原則(Least Knowledge Principle,LKP)
迪米特法則也稱為最少知識原則,雖然名字不同,但描述的是同一個規則:一個對象應該對其他對象有最少的了解。通俗地講,一個類應該對自己需要耦合或調用的類知道得最少,你(被耦合或調 用的類)的内部是如何複雜都和我沒關系,那是你的事情,我就知道你提供的這麼多public方法,我就調用這麼多,其他的我一概不關心
迪米特法則對類的低耦合提出了明确的要求,其包含以下4層含義。
-
隻和朋友交流
迪米特法則還有一個英文解釋是:Only talk to your immediate friends(隻與直接的朋友通 信。)
-
朋友間也是有距離的
一個類隻和朋友交流,不與陌生類交流,不要出現getA().getB().getC().getD()這種情況(在一種極端的情況下允許出現這種通路,即每一個點号後面的傳回類型都相同),類與類之間的關系是建立在類間的,而不是方法間,是以一個方法盡量不引入一個類中不存在 的對象,當然,JDK API提供的類除外。
-
是自己的就是自己的
如果一個方法放在本類中,既不增加類間關 系,也對本類不産生負面影響,那就放置在本類中。
- 謹慎使用Serializable
最佳實踐
迪米特法則的核心觀念就是類間解耦,弱耦合,隻有弱耦合了以後,類的複用率才可以提高。其要求的結果就是産生了大量的中轉或跳轉類,導緻系統的複雜性提高,同時也為維 護帶來了難度。讀者在采用迪米特法則時需要反複權衡,既做到讓結構清晰,又做到高内聚低耦合。
不知道大家有沒有聽過這樣一個理論:“任何兩個素不相識的人中間最多隻隔着6個人, 即隻通過6個人就可以将他們聯系在一起”,這就是著名的“六度分隔理論”。如果将這個理論 應用到我們的項目中,也就是說,我和我要調用的類之間最多有6次傳遞。呵呵,這隻能讓 大家當個樂子來看,在實際應用中,如果一個類跳轉兩次以上才能通路到另一個類,就需要 想辦法進行重構了,為什麼是兩次以上呢?因為一個系統的成功不僅僅是一個标準或是原則 就能夠決定的,有非常多的外在因素決定,跳轉次數越多,系統越複雜,維護就越困難,所 以隻要跳轉不超過兩次都是可以忍受的,這需要具體問題具體分析
開閉原則(Open Closed Principle,OCP)
開閉原則是Java世界裡最基礎的設計 原則,它指導我們如何建立一個穩定的、靈活的系統,先來看開閉原則的定義:
Software entities like classes,modules and functions should be open for extension but closed for modifications.
一個軟體實體如類、子產品和函數應該對擴充開放,對修改關閉。并不意味着不做任何修改,低層子產品的變更,必然要有高層子產品進行耦合,否則就是一個孤立無意義的代碼片段。
開閉原則的重要性
-
開閉原則可以提高複用性
在面向對象的設計中,所有的邏輯都是從原子邏輯組合而來的,而不是在一個類中獨立 實作一個業務邏輯。隻有這樣代碼才可以複用,粒度越小,被複用的可能性就越大。那為什 麼要複用呢?減少代碼量,避免相同的邏輯分散在多個角落,避免日後的維護人員為了修改 一個微小的缺陷或增加新功能而要在整個項目中到處查找相關的代碼,然後發出對開發人 員“極度失望”的感慨。那怎麼才能提高複用率呢?縮小邏輯粒度,直到一個邏輯不可再拆分 為止。
-
開閉原則可以提高可維護性
一款軟體投産後,維護人員的工作不僅僅是對資料進行維護,還可能要對程式進行擴 展,維護人員最樂意做的事情就是擴充一個類,而不是修改一個類
- 面向對象開發的要求
最佳實踐
開閉原則是一個終極目标,任何人包括大師級人物都無法百分之百做到,但朝這個方向 努力,可以非常顯著地改善一個系統的架構,真正做到“擁抱變化”。
總結
軟體設計最大的難題就是應對需求的變化,但是紛繁複雜的需求變化又是不可預料的。 我們要為不可預料的事情做好準備,這本身就是一件非常痛苦的事情,但是大師們還是給我 們提出了非常好的6大設計原則以及23個設計模式來“封裝”未來的變化,我們在前5章中講過 如下設計原則。
- Single Responsibility Principle:單一職責原則
- Open Closed Principle:開閉原則
- Liskov Substitution Principle:裡氏替換原則
- Law of Demeter:迪米特法則
- Interface Segregation Principle:接口隔離原則
- Dependence Inversion Principle:依賴倒置原則
把這6個原則的首字母(裡氏替換原則和迪米特法則的首字母重複,隻取一個)聯合起 來就是SOLID(solid,穩定的),其代表的含義也就是把這6個原則結合使用的好處:建立 穩定、靈活、健壯的設計。