設計模式六大原則簡單總結

1.單一職責原則(Single Responsibility Principle)

There should never be more than one reason for a class to change.
應該有且僅有一個原因引起類的變更。這裏也包括接口、方法。

優點

  1. 降低類的複雜性。職責單一,定義明確,自然就變得簡單。由此也陸續引出以下的優點。
  2. 提高可讀性
  3. 提高可維護性。職責單一,將不會出現,修改職責A的邏輯會影響到不需要改變的職責B的邏輯。
  4. 提高擴展性

類的單一職責原則

比如,對象的屬性和行爲,就應該放到兩個類裏處理。比如對於用戶,用戶的屬性:id、name、age就應該放到UserBO中。而對於用戶行爲的處理:添加權限、修改密碼,就應該放到UserBiz類中處理。因爲如果都放到同一個類中處理,則屬性和行爲的變更都會引起類的變更,便不符合有且只有一個原因引起變更。

方法的單一職責原則

原方法:changeUserInfo()。其中,修改用戶信息,同時修改了用戶的名稱、密碼、聯繫方式等等。應改爲:

  • changeUserName()
  • changeUserPassWord()
  • changeUserPhone()

便於明確職責。

個人理解

如果職責劃分過細,或者過於追求單一職責原則,會導致類和方法過多。因此,在貫徹單一職責原則的過程中,我們應該首先確認合適的職責範圍,然後在按照單一職責原則進行設計。

2.里氏替換原則(Liskov Substitution Principle,LSP)

定義1:

如果對每一個類型爲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.
所有引用基類的地方必須透明的使用其子類的對象。即:只要父類能出現的地方子類也可以出現,而且替換爲子類不會產生任何錯誤或異常,但是反過來就不行,有子類出現的地方,父類未必就能適應

關於繼承

實質上,里氏替換原則是對繼承的使用做出了一定的規範。繼承的優點在於,減少類創建的工作量、提高代碼複用性、提高類的可擴展性等。但繼承也有缺點,即:繼承是侵入性的,只要繼承,就必須擁有父類的所有方法和屬性;降低了代碼的靈活性,子類必須擁有父類的屬性和方法,讓子類有了一些約束;增加了耦合性,當父類的常量,變量和方法被修改了,需要考慮子類的修改,這種修改可能帶來非常糟糕的結果,要重構大量的代碼。

里氏替換原則的四個規範

簡單來說,就是子類可以擴展父類的功能,但不能改變父類原有的功能。
對於繼承會導致的問題,里氏替換原則規範了四個規範。
1. 子類必須完全實現父類的方法。(即正向,引用基類的地方必須能透明地使用子類對象,且子類不能隨意覆蓋父類的方法,這樣導致子類無法完全替換父類)
2. 子類可以有自己的個性。(即反向,必須引用子類的地方不能以基類代替)
3. 覆蓋或者實現父類的方法時輸入參數可以被放大。(一旦輸入參數不相同,那麼就不是重寫而是重載,那麼如果想實現里氏替換原則,就必須讓父類的參數更小,因爲這樣纔可以讓替換子類的時候仍使用該方法。例如:父類方法入參爲實現類(HashMap),子類方法入參可以爲接口(Map),或者子類方法入參(HashMap)爲父類方法入參(ConcurrentHashMap)的父類)
4. 覆蓋或者實現父類的方法時輸出結果可以被縮小。

反例

java.sql.Time類,繼承了java.util.Date類。但是他重寫了getDate()、getDay()等方法,直接拋出了java.lang.IllegalArgumentException()異常,破壞了里氏替換原則。

3.依賴倒置原則(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 abstracts.

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

依賴倒置原則在java語言中,表現爲:

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

依賴倒置原則的核心思想是面向接口編程!

舉個例子

比如,作爲一個苦逼的碼農(Coder類),每天下班回家(goHome)都要騎摩拜。

public class Mobike {
    public void drive(){
        System.out.print("騎摩拜");
    }
}

public class Coder {
    public void goHome(Mobike mobike){
        mobike.drive();
        System.out.println("回家!");
    }
}

public void offDuty() {
    Coder coder = new Coder();
    Mobike mobike = new Mobike();
    coder.goHome(mobike);
}

運行dip方法,則會輸出“騎摩拜回家!”
然而有一臺,公司上市了!苦逼碼農翻身做主人,財務自由了!於是他買了輛車,以後每天開車回家,美滋滋。

public class Car {
    public void drive(){
        System.out.print("開車");
    }
}

但是goHome方法的入參只能接受Mobike類型的交通工具,Coder和Mobike的耦合性過高,產生了強依賴,所以買了車也沒法馬上開。
如果我們的Coder一開始擁有這樣的駕駛能力,小汽車和摩拜也這樣設計呢?

public interface Vehicle {
    void drive();
}
public class Mobike implements Vehicle{
    public void drive(){
        System.out.print("騎摩拜");
    }
}
public class Car implements Vehicle{
    public void drive() {
        System.out.print("開車");
    }
}
public class Coder {
    public void goHome(Vehicle vehicle){
        vehicle.drive();
        System.out.println("回家!");
    }
}
public void offDuty() {
    Coder coder = new Coder();
    Car mobike = new Car();
    coder.goHome(mobike);
}

這樣的話,我們的高端碼農就可以肆意妄爲的想騎車就騎車想開車就開車嘞。

依賴倒置原則的優點

  • 降低類之間的耦合性,提高系統的穩定性,降低修改程序造成的風險
  • 降低並行開發引起的風險與難度

依賴倒置原則的經驗

  • 每個類儘量都有接口或者抽象類,或者抽象類和接口兩都具備。在使用時儘量依賴接口或抽象類使用。
  • 變量的表面類型儘量是接口或者抽象類
  • 任何類都不應該從具體類派生(視情況而定)
  • 儘量不要覆寫基類的方法 。覆蓋基類的方法會影響依賴的穩定性。
  • 結合里氏替換原則使用

4.接口隔離原則

  • 定義1:Clients should not be forced to depend upon interfaces that they
    don’t use.
    客戶端不應該依賴它不需要的接口。
    那依賴什麼呢?依賴它需要的接口,客戶端需要什麼接口就提供什麼接口,把不需要的接口剔除,那就需要對接口進行細化,保證其純潔性。

  • 定義2:The dependency of one class to another one should depend on the
    smallest possible interface.
    類間的依賴關係應該建立在最小的接口上。
    它要求是最小的接口,也是要求接口細化,接口純潔。

這裏的接口指的是:

  • 實例接口(Object Interface) :在 Java 中聲明一個類,然後用 new 關鍵字產生一個實例,它是對一類事物的描述,可以看成是一個接口
  • 類接口(Class Interface):Java中常使用的interface關鍵字定義的接口
    即:建立單一接口,不要建立臃腫龐大的接口。再通俗的說就是接口儘量細化,同時接口中的方法儘量少。我們要爲各個類建立專用的接口,而不要試圖去建立一個很龐大的接口供所有依賴它的類去調用。

接口隔離即注意控制接口粒度大小,是爲了防止我們封裝過度,但是同時設計也是有限度的,我們在實現接口隔離原則時,一定要先滿足單一職責原則

5.迪米特法則(Law of Demeter,LoD)

一個對象應該對其他對象有最少的瞭解。也叫最少知識原則(Low knowledge Principle,LKP),即一個類對自己需要耦合或調用的類知道的越少越好。或者用另一個解釋:Only talk to your immediate friends(只與直接朋友通信)。
迪米特法則是對對象之間的耦合度進行限制,儘量降低類與類之間的耦合。

什麼是朋友

直接的朋友:每個對象都會與其他對象有耦合關係,只要兩個對象之間有耦合關係,我們就說這兩個對象之間是朋友關係。耦合的方式很多,依賴、關聯、組合、聚合等。其中,我們稱出現成員變量、方法參數、方法返回值中的類爲直接的朋友。
而出現在局部變量中的類則不是直接的朋友。也就是說,陌生的類最好不要作爲局部變量的形式出現在類的內部。

注意

既然要避免與非直接的朋友通信,那就勢必要通過直接的朋友作爲中介來間接的與非直接的朋友通信,這樣會導致系統複雜度變大。所以在採用迪米特法則時要反覆權衡,既做到結構清晰,又要高內聚低耦合。

6.開閉原則

Software entities like classes,modules and functions should be open for extension but closed for modifications.
一個軟件實體如類,模塊和函數應該對擴展開放,對修改關閉。
即:一個軟件實體應該通過擴展來實現變化,而不是通過修改已有的代碼來實現變化的

爲什麼使用開閉原則

  1. 開閉原則是最基礎的設計原則,其它的五個設計原則都是開閉原則的具體形態。
    也就是說其它的五個設計原則是指導設計的工具和方法,而開閉原則纔是其精神領袖。依照java語言的稱謂,開閉原則是抽象類,而其它的五個原則是具體的實現類。
  2. 開閉原則可以提高複用性
    在面向對象的設計中,所有的邏輯都是從原子邏輯組合而來,不是在一個類中獨立實現一個業務邏輯。只有這樣的代碼纔可以複用,粒度越小,被複用的可能性越大。
  3. 開閉原則可以提高維護性
    擴展一個類比修改一個類要好的多
  4. 面向對象開發的要求

總結

  • 單一職責原則告訴我們實現類要職責單一
  • 里氏替換原則告訴我們不要破壞繼承體系
  • 依賴倒置原則告訴我們要面向接口編程
  • 接口隔離原則告訴我們在設計接口的時候要精簡單一
  • 迪米特法則告訴我們要降低耦合
  • 開閉原則是總綱,他告訴我們要對擴展開放,對修改關閉

參考文檔

專欄:6大設計原則詳解
以及一些內部資料

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