天天看點

設計模式原則(下)

參考連結:https://www.imooc.com/read/53/article/1079

1、基本概念

本節繼續介紹設計模式的七大原則的基本概念,上一節重要講了開閉原則、單一職責原則、裡氏替換原則、依賴倒置原則,這一節我們主要了解下接口隔離原則、迪米特法則以及合成複用原則。

本節以介紹基本概念為主,其中會加入部分示範代碼、uml 類圖講解,能了解基本概念即可。後續章節設計模式的講解會詳細介紹這些原則的應用。

本節主要内容有:

  • 什麼是接口隔離原則、迪米特法則以及合成複用原則
  • 為何要遵循這些原則

2、接口隔離原則

接口隔離原則(Interface Segregation Principle,ISP)的定義是用戶端不應該依賴它不需要的接口,類間的依賴關系應該建立在最小的接口上。 簡單來說就是建立單一的接口,不要建立臃腫龐大的接口。也就是接口盡量細化,同時接口中的方法盡量少,保持接口純潔性。

我們所講的接口主要分為兩大類,一是執行個體接口,比如使用 new 關鍵字産生一種執行個體,被 new 的類就是執行個體類的接口。從這個角度出發的話,java 中的類其實也是一種接口。二是類接口,java 中常常使用 interface 關鍵字定義。

舉個栗子來說,我們使用接口 IPrettyGirl 來描述美女,剛開始類圖可能描述如下:

設計模式原則(下)

但是發現該接口中包含對美女的外觀描述、内在美描述等,幾乎将美女的所有特性全部納入,這顯然不是一個很好的設計規範,比如在唐朝,在那個以豐腴為美的時代對美的了解就不同,就會出現單純 goodLooking 過關就是美女的結果,是以這裡我們需要将接口隔離拆分。将一個接口拓展為兩個,增加系統靈活性及可維護性。

設計模式原則(下)

這裡我們将美女接口拆分為内在美、外在美兩個接口,系統靈活性提高了,另外接口間還能使用繼承實作聚合,系統拓展性也得到了增強。

接口隔離原則總結:

  • 接口盡量粒度化,保持接口純潔性
  • 接口要高内聚,即減少對外互動

3、迪米特法則

迪米特法則(Law of Demeter,LOD),有時候也叫做最少知識原則(Least Knowledge Principle,LKP),它的定義是:一個軟體實體應當盡可能少地與其它實體發生互相作用。迪米特法則的初衷在于降低類之間的耦合。

舉個栗子,拿教師點名來講,體育老師需要清點班上學生人數,教師一般不是自己親自去數,而是委托組長或班長等人去清點,即教師通過下達指令至班長要求清點人數:

public class Girl {

}

public class GroupLeader {

    private final List<Girl> girls;

    public GroupLeader(List<Girl> girls) {
        this.girls = girls;
    }

    public void countGirls() {
        System.out.println("The sum of girls is " + girls.size());
    }
}

public class Teacher {

    public void command(GroupLeader leader){
        leader.countGirls();
    }
}

public class Main {

    public static void main(String[] args) throws Exception {
        Teacher teacher = new Teacher();
        GroupLeader groupLeader = new GroupLeader(Arrays.asList(new Girl(), new Girl()));
        teacher.command(groupLeader);
    }
}
           
設計模式原則(下)

上述例子中,如果去掉 GroupLeader 這個中間人角色,教師就會直接去清點人數,這樣做會違反迪米特法則。

迪米特法則總結:

  • 類定義時盡量内斂,少使用 public 權限修飾符,盡量使用 private、protected 等。

4、合成複用原則

合成複用原則是通過将已有的對象納入新對象中,作為新對象的成員對象來實作的,新對象可以調用已有對象的功能,進而達到複用。 原則是盡量首先使用合成 / 聚合的方式,而不是使用繼承。

合成和聚合都是關聯的特殊種類。合成是值的聚合(Aggregation by Value),而複合是引用的聚合(Aggregation by Reference)。

都知道,類之間有三種基本關系,分别是:關聯(聚合群組合)、泛化(與繼承同一概念)、依賴。

這裡我們提一下關聯關系,客觀來講,大千世界中的兩個實體之間總是有着千絲萬縷的關系,歸納到軟體系統中就是兩個類之間必然存在關聯關系。如果一個類單向依賴另一個類,那麼它們之間就是單向關聯。如果彼此依賴,則為互相依賴,即雙向關聯。

關聯關系包括兩種特例:聚合群組合。聚合,用來表示整體與部分的關系或者 “擁有” 關系。其中,代表部分的對象可能會被代表多個整體的對象所擁有,但是并不一定會随着整體對象的銷毀而銷毀,部分的生命周期可能會超越整體。好比班級和學生,班級銷毀或解散後學生還是存在的,學生可以繼續存在某個教育訓練機構或步入社會,生命周期不同于班級甚至大于班級。

合成,用來表示一種強得多的 “擁有” 關系。其中,部分和整體的生命周期是一緻的,一個合成的新的對象完全擁有對其組成部分的支配權,包括建立和泯滅。好比人的各個器官組成人一樣,一旦某個器官衰竭,人也不複存在,這是一種 “強” 關聯。

設計模式原則(下)

如上圖所示,Heart 和 Student、Teacher 都是一種 “強” 關聯,人不能擺脫心髒而存在,即組合關系,而 Student 和 Class、School 是一種 “弱” 關聯,脫離了學校、班級,學生還能屬于社會或其他團體,即聚合關系。

合成複用原則總結:

  • 新對象可以調用已有對象的功能,進而達到對象複用

5、總結

總結下這兩節的内容,我們一共介紹了 7 種設計原則,它們分别為開閉原則、單一職責原則、裡氏替換原則、依賴倒置原則、接口隔離原則和本節所介紹的合成複用原則。

各種原則要求的側重點不同,總地來說:

1、開閉原則是核心,對拓展開放對修改關閉是軟體設計、後期拓展的基石;

2、單一職責原則就要求我們設計接口,制定子產品功能時保持子產品或者接口功能單一,接口設計或功能設計盡量保持原子性,修改一處不能影響全局或其它子產品;

3、裡氏替換原則和依賴倒置原則,按照作者的了解,這倆原則總的是要求我們要面向接口、面向抽象程式設計,設計程式的時候盡可能使用基類或者接口進行對象的定義或引用,而不是具體的實作,否則實作一旦有變更,上層調用者就必須做出對應變更,這樣一來,整個子產品可能都需要重新調整,非常不利于後期拓展。

設計模式原則(下)

4、接口隔離原則具體應用到程式中,比如我們在傳統 mvc 開發時,service 層調用 dao 層一般會使用接口進行調用,各層之間盡量面向接口通信,其實也是一種降低子產品耦合的方法;

5、迪米特法則的初衷也是為了降低子產品耦合,代碼示例中我們引入了類似 “中間人” 的概念,上層子產品不直接調用下層子產品,而是引入第三方進行代辦,這也是為了降低子產品的耦合度;

6、合成複用原則一節,我們介紹了聚合、組合的概念,聚合是一種弱關聯,而組合是一種強關聯,表現在 UML 類圖上的話聚合是使用空心四邊形加箭頭表示,而組合是使用實心四邊形加箭頭表示,合成複用原則總的就是要求我們盡量利用好已有對象,進而達到功能複用,具體是聚合還是組合,還是一般關聯,就要看具體情況再定了。

這 7 種設計原則是軟體設計必須盡量遵循的原則。

繼續閱讀