參考連結: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 種設計原則是軟體設計必須盡量遵循的原則。