Java設計模式-六大原則

一、單一職能原則(Single Responsibility Principle, SRP)

定義

There should never be more than one reason for a class to change.數據庫

應該有且僅有一個緣由引發類的變動編程

換言之,也就是一個接口或類只有一個職責設計模式

好處

  • 類的複雜性下降,實現什麼職責都有清晰明確的定義;
  • 可讀性提升,複雜性下降,那固然可讀性提升了;
  • 可維護性提升,可讀性提升,那固然更容易維護了;
  • 變動引發的風險下降,變動時必不可少的,若是接口的單一職責作得好,一個接口修改只對相應的實現類有影響,對其餘的接口無影響,這對系統的擴展性、維護性都有很是大的幫助。

最佳實踐

職責的劃分很難,要想徹底符合單一職責的設計更難,原則是接口必定要作到單一職責,類的設計儘可能作到只有一個緣由引發變化markdown

二、里氏替換原則(LiskovSubstitution Principle,LSP)

繼承的利與弊

  • 優勢
    • 代碼共享,減小建立類的工做量,每一個子類都擁有父類的方法和屬性;
    • 提升代碼的重用性;
    • 子類能夠形似父類,但又異於父類;
    • 提升代碼的可擴展性,實現父類的方法就能夠「隨心所欲」了;
    • 提升產品或項目的開放性。
  • 缺點
    • 繼承是侵入性的。只要繼承,就必須擁有父類的全部屬性和方法;
    • 下降代碼的靈活性。子類必須擁有父類的屬性和方法,讓子類自由的世界中多了些約束;
    • 加強了耦合性。當父類的常量、變量和方法被修改時,須要考慮子類的修改,並且在缺少規範的環境下,這種修改可能帶來很是糟糕的結果——大段的代碼須要重構。

定義

If for each object o1 of type S there is an object o2 oftype T such that for all programs P defined in terms of T,the behavior of P is unchanged when o1 issubstituted for o2 then S is a subtype of T.架構

若是對每個類型爲S的對象o1,都有類型爲T的對象o2,使得以T定義的全部程序P在全部的對象o1都代換成o2時,程序P的行爲沒有發生變化,那麼類型S是類型T的子類型。函數

以上是LSP"最正宗"的定義,可是可能不太容易理解,它還有另一種相對清晰明確的定義:工具

Functions that use pointers or references to base classes must be able to useobjects of derived classes without knowing it.性能

全部引用基類的地方必須能透明地使用其子類的對象。spa

通俗點講,只要父類能出現的地方子類就能夠出現,並且替換爲子類也不會產生任何錯誤或異常,使用者可能根本就不須要知道是父類仍是子類。可是,反過來就不行了,有子類出現的地方,父類未必就能適應。(LSP能夠正着用但不能反着用)設計

規範

里氏替換原則爲良好的繼承定義了一個規範,一句簡單的定義包含了4層含義:

  • 子類必須徹底實現父類的方法

    若是子類不能完整地實現父類的方法,或者父類的某些方法在子類中已經發生「畸變」,則建議斷開父子繼承關係,採用依賴、彙集、組合等關係代替繼承。

  • 子類能夠有本身的個性

  • 覆蓋或實現父類的方法時輸入參數能夠被放大**(重載而非覆蓋)**

  • 覆寫或實現父類的方法時輸出結果能夠被縮小

    父類的一個方法的返回值是一個類型T,子類的相同方法(重載或覆寫)的返回值爲S,那麼里氏替換原則就要求S必須小於等於T,也就是說,要麼S和T是同一個類型,要麼S是T的子類。

最佳實踐

在項目中,採用里氏替換原則時,儘可能避免子類的「個性」,一旦子類有「個性」,這個子類和父類之間的關係就很難調和了,把子類當作父類使用,子類的「個性」被抹殺——委屈了點;把子類單獨做爲一個業務來使用,則會讓代碼間的耦合關係變得撲朔迷離——缺少類替換的標準。

三、依賴倒置原則(Dependence Inversion Principle,DIP)

定義

High level modules should not depend upon low level modules.Both should depend uponabstractions.Abstractions should not depend upon details.Details should depend upon abstractions.

  • 高層模塊不該該依賴低層模塊,二者都應該依賴其抽象;
  • 抽象不該該依賴細節;
  • 細節應該依賴抽象。

依賴倒置原則在Java中的體現:

  • 模塊間的依賴經過抽象發生,實現類之間不發生直接的依賴關係,其依賴關係是經過接口或抽象類產生的;
  • 接口或抽象類不依賴於實現類;
  • 實現類依賴接口或抽象類。

更加精簡的定義就是「面向接口編程」。

依賴的三種寫法

  • 構造函數傳遞依賴對象(構造函數注入),在類中經過構造函數聲明依賴對象
  • Setter方法傳遞依賴對象(Setter依賴注入),在抽象中設置Setter方法聲明依賴關係
  • 在接口的方法中聲明依賴對象(接口注入)

最佳實踐

依賴倒置原則的本質就是經過抽象(接口或抽象類)使各個類或模塊的實現彼此獨立,不互相影響,實現模塊間的鬆耦合,咱們怎麼在項目中使用這個規則呢?只要遵循如下的幾個規則就能夠:

  • 每一個類儘可能都有接口或抽象類,或者抽象類和接口二者都具有

    這是依賴倒置的基本要求,接口和抽象類都是屬於抽象的,有了抽象纔可能依賴倒置

  • 變量的表面類型儘可能是接口或者是抽象類

  • 任何類都不該該從具體類派生

  • 儘可能不要覆寫基類的方法

  • 結合里氏替換原則使用

    父類出現的地方子類就能出現,再DIP,咱們能夠得出這樣一個通俗的規則: 接口負責定義public屬性和方法,而且聲明與其餘對象的依賴關係,抽象類負責公共構造部分的實現,實現類準確的實現業務邏輯,同時在適當的時候對父類進行細化。

依賴倒置原則是6個設計原則中最難以實現的原則,它是實現開閉原則的重要途徑,依賴倒置原則沒有實現,就別想實現對擴展開放,對修改關閉。在項目中,你們只要記住是「面向接口編程」就基本上抓住了依賴倒置原則的核心。

四、接口隔離原則(Interface Segregations Principle, ISP)

定義

接口隔離原則有如下兩種定義:

Clients should not be forced to depend upon interfaces that they don't use.

客戶端不該該依賴它不須要的接口。

解釋:客戶端須要什麼接口就提供什麼接口,把不須要的接口剔除掉,那就須要對接口進行細化,保證其純潔性;

The dependency of one class to another one should depend on the smallest possible interface.

類間的依賴關係應該創建在最小的接口上。

解釋:要求是最小的接口,也是要求接口細化,接口純潔,與第一個定義一模一樣,只是一個事物的兩種不一樣描述。

歸納:創建單一接口,不要創建臃腫龐大的接口。再通俗一點講:接口儘可能細化,同時接口中的方法儘可能少。

接口隔離原則 VS 單一職責原則

  • 接口隔離原則與單一職責的審視角度是不相同的

  • 單一職責要求的是類和接口職責單一,注重的是職責,這是業務邏輯上的劃分;

  • 接口隔離原則要求接口的方法儘可能少。

例如一個接口的職責可能包含10個方法,這10個方法都放在一個接口中,而且提供給多個模塊訪問,各個模塊按照規定的權限來訪問,在系統外經過文檔約束「不使用的方法不要訪問」,按照單一職責原則是容許的,按照接口隔離原則是不容許的,由於它要求「儘可能使用多個專門的接口」。專門的接口指什麼?就是指提供給每一個模塊的都應該是單一接口,提供給幾個模塊就應該有幾個接口,而不是創建一個龐大的臃腫的接口,容納全部的客戶端訪問。

// TODO 仍是感受差不太多...

規範

  • 接口要儘可能小

    • 要有限度
    • 不能違背單一職責原則(業務上的劃分)
  • 接口要高內聚

    高內聚具體到接口隔離原則就是,要求在接口中儘可能少公佈public方法,接口是對外的承諾,承諾越少對系統的開發越有利,變動的風險也就越少,同時也有利於下降成本。

  • 定製服務(只提供訪問者須要的方法)

    舉例說明:一個用於查詢用戶的接口,應該提供具體的根據用戶ID查詢或者根據用戶名查詢等於業務相關的特定接口,而不是提供一個帶有Map(或者說相似Mybatis Example)參數的查詢接口

  • 接口設計是有限度的

    • 接口的設計粒度越小,系統越靈活;
    • 可是,靈活的同時也帶來告終構的複雜化,開發難度增長,可維護性下降,這不是一個項目或產品所指望看到的;
    • 因此接口設計必定要注意適度,這個「度」如何來判斷呢?根據經驗和常識判斷,沒有一個固化或可測量的標準。

最佳實踐

接口隔離原則是對接口的定義,同時也是對類的定義,接口和類儘可能使用原子接口或原子類來組裝。可是,這個原子該怎麼劃分是設計模式中的一大難題,在實踐中能夠根據如下幾個規則來衡量:

  • 一個接口只服務於一個子模塊或業務邏輯;
  • 經過業務邏輯壓縮接口中的public方法,接口時常去回顧,儘可能讓接口達到「滿身筋骨肉」,而不是「肥嘟嘟」的一大堆方法;
  • 已經被污染了的接口,儘可能去修改,若變動的風險較大,則採用適配器模式進行轉化處理;
  • 瞭解環境,拒絕盲從。每一個項目或產品都有特定的環境因素,環境不一樣,接口拆分的標準就不一樣。深刻了解業務邏輯,結合實際狀況進行接口設計。

五、迪米特法則(Law of Demeter,LoD)

也稱爲最少知識原則(Least KnowledgePrinciple,LKP)

定義

一個對象應該對其餘對象有最少的瞭解

通俗地講,一個類應該對本身須要耦合或調用的類知道得最少,你(被耦合或調用的類)的內部是如何複雜都和我不要緊,那是你的事情,我就知道你提供的這麼多public方法,我就調用這麼多,其餘的我一律不關心。

含義

  • 只和朋友交流

    迪米特法則還有一個英文解釋是:

    Only talk to your immediate friends.

    只與直接的朋友通訊。

    一個類只和朋友交流,不與陌生類交流,不要出現getA().getB().getC().getD()這種狀況(在一種極端的狀況下容許出現這種訪問,即每個點號後面的返回類型都相同),類與類之間的關係是創建在類間的,而不是方法間,所以一個方法儘可能不引入一個類中不存在的對象,固然,JDK API提供的類除外。

  • 朋友間也是有距離的

    迪米特法則要求類「羞澀」一點,儘可能不要對外公佈太多的public方法和非靜態的public變量,儘可能內斂,多使用private、package-private、protected等訪問權限。

  • 是本身的就是本身的

    在實際應用中常常會出現這樣一個方法:放在本類中也能夠,放在其餘類中也沒有錯,那怎麼去衡量呢?你能夠堅持這樣一個原則:若是一個方法放在本類中,既不增長類間關係,也對本類不產生負面影響,那就放置在本類中。

  • 謹慎使用Serializable

最佳實踐

  • 迪米特法則的核心觀念就是類間解耦,弱耦合,只有弱耦合了之後,類的複用率才能夠提升。其要求的結果就是產生了大量的中轉或跳轉類,致使系統的複雜性提升,同時也爲維護帶來了難度。在採用迪米特法則時須要反覆權衡,既作到讓結構清晰,又作到高內聚低耦合。

  • 迪米特法則要求類間解耦,但解耦是有限度的,除非是計算機的最小單元——二進制的0和1。那纔是徹底解耦,在實際的項目中,須要適度地考慮這個原則,別爲了套用原則而作項目。原則只是供參考,若是違背了這個原則,項目也未必會失敗,這就須要你們在採用原則時反覆度量,不遵循是不對的,嚴格執行就是「過猶不及」。

六、開閉原則(Open Closed Principle, OCP)

開閉原則是Java世界裏最基礎的設計原則,它指導咱們如何創建一個穩定的、靈活的系統

定義

Software entities like classes,modules and functions should be open for extension but closed formodifications.

一個軟件實體如類、模塊和函數應該對擴展開放,對修改關閉。

換句話說,一個軟件實體應該經過擴展來實現變化,而不是經過修改已有的代碼來實現變化。

重要性

開閉原則是最基礎的一個原則,前面節介紹的五個原則都是開閉原則的具體形態,也就是說前五個原則就是指導設計的工具和方法,而開閉原則纔是其精神領袖。換一個角度來理解,依照Java語言的稱謂,開閉原則是抽象類,其餘五大原則是具體的實現類,開閉原則在面向對象設計領域中的地位就相似於牛頓第必定律在力學、勾股定律在幾何學、質能方程在狹義相對論中的地位,其地位無人能及。

如何使用開閉原則

  • 抽象約束

    • 經過接口或抽象類約束擴展,對擴展進行邊界限定,不容許出如今接口或抽象類中不存在的public方法;
    • 參數類型、引用對象儘可能使用接口或者抽象類,而不是實現類;
    • 抽象層儘可能保持穩定,一旦肯定即不容許修改。
  • 元數據(metadata)控制模塊行爲

    何謂元數據?就是用來描述環境和數據的數據,通俗地說就是配置參數,參數能夠從文件中得到,也能夠從數據庫中得到。

  • 制定項目章程(團隊約定)

  • 封裝變化

    • 將相同的變化封裝到一個接口或抽象類中;
    • 將不一樣的變化封裝到不一樣的接口或抽象類中,不該該有兩個不一樣的變化出如今同一個接口或抽象類中。
    • 封裝變化,也就是受保護的變化(protected variations),找出預計有變化或不穩定的點,咱們爲這些變化點建立穩定的接口,準確地講是封裝可能發生的變化,一旦預測到或「第六感」發覺有變化,就能夠進行封裝,23個設計模式都是從各個不一樣的角度對變化進行封裝的

最佳實踐

  • 開閉原則也只是一個原則

    開閉原則只是精神口號,實現擁抱變化的方法很是多,並不侷限於這6大設計原則,可是遵循這6大設計原則基本上能夠應對大多數變化。所以,咱們在項目中應儘可能採用這6大原則,適當時候能夠進行擴充,例如經過類文件替換的方式徹底能夠解決系統中的一些缺陷。你們在開發中比較經常使用的修復缺陷的方法就是類替換,好比一個軟件產品已經在運行中,發現了一個缺陷,須要修正怎麼辦?若是有自動更新功能,則能夠下載一個.class文件直接覆蓋原有的class,從新啓動應用(也不必定非要從新啓動)就能夠解決問題,也就是經過類文件的替換方式修正了一個缺陷,固然這種方式也能夠應用到項目中,正在運行中的項目發現須要增長一個新功能,經過修改原有實現類的方式就能夠解決這個問題,前提條件是:類必須作到高內聚、低耦合,不然類文件的替換會引發不可預料的故障。

  • 項目規章很是重要

    若是你是一位項目經理或架構師,應儘可能讓本身的項目成員穩定,穩定後才能創建高效的團隊文化,章程是一個團隊全部成員共同的知識結晶,也是全部成員必須遵照的約定。優秀的章程能帶給項目帶來很是多的好處,如提升開發效率、下降缺陷率、提升團隊士氣、提升技術成員水平,等等。

  • 預知變化

    在實踐中過程當中,架構師或項目經理一旦發現有發生變化的可能,或者變化曾經發生過,則須要考慮現有的架構是否能夠輕鬆地實現這一變化。架構師設計一套系統不只要符合現有的需求,還要適應可能發生的變化,這纔是一個優良的架構。

開閉原則是一個終極目標,任何人包括大師級人物都沒法百分之百作到,但朝這個方向努力,能夠很是顯著地改善一個系統的架構,真正作到「擁抱變化」。

七、總結

首先,不管是6大設計原則仍是23種設計模式,根本目標其實就是一個"擁抱變化",包括需求的變化、運行環境的變化、性能要求的提高等等,實現一個需求並不難,可是當變化來臨時,可否泰然處之,那就是個技術活了。

其次,"擁抱變化"落實到代碼層面是什麼?——你的代碼是***可維護的、可擴展的***,仍是說"牽一髮動全身",一點小的改動不少東西都要跟着變更。

再次,要作到"擁抱變化",讓你的代碼很容易維護和擴展,核心理念就是***"高內聚、低耦合"***,以及"***面向接口編程(面向抽象編程)***」

最後,設計原則、設計模式是前輩總結的"經驗",但不是"條款",儘量遵循這些規範會讓你的設計無限接近完美,但世界上本就沒有十全十美的東西,凡事都要有個度,不要認死理,不要爲了"套模式"而應用設計模式,要具體問題具體分析,根據實際狀況進行權衡。

設計作得再漂亮,代碼寫得再完美,項目作得再符合標準,一旦項目虧本,產品投入大於產出,那總體就是扯淡!

參考文獻:《設計模式之禪》

相關文章
相關標籤/搜索