類與類之間的 6 種關係

1. 前言

2016 年 8 月我去面試 Android 的時候,遇到一個 Java 大牛,問過我這個問題。我確實不知道類與類之間竟然會有 6 種關係。面試後,雖然查了一下,但是依然不理解:類與類之間這 6 種關係是怎麼總結出來的。

這篇文字就用來介紹類與類之間的 6 種關係,如何使用 UML 表示類與類之間的 6 種關係,如何更好地記憶 6 種關係的表示。

2. 正文

2.1 繼承(generalization)

這個大家都不陌生,可以說是非常熟悉了。使用繼承,我們能夠以現有類爲基礎(獲取它的全部可繼承的成員),通過添加或修改一些成員來創建新類。源類常被稱爲基類、超類或者父類,導出類常被稱爲繼承類或者子類。以經典的幾何形的例子來說明:

在上面的圖中,Shape 類是父類,而 CircleSquareTriangle 是三個子類。我們使用空心三角箭頭+實線,來表示子類與父類之間的繼承關係。需要注意的是,箭頭是由子類指向父類的。

通過繼承,子類獲得了父類的全部可繼承的成員;但是,子類難道僅僅是爲了獲取父類的全部可繼承的成員而已嗎?

顯然不是,如果僅僅那樣,何不直接使用父類呢?

實際上,子類通常會和父類產生差異,有兩種辦法可以實現。

第一種產生差異的辦法是直接在子類中添加新的方法(這些新方法並不是父類的方法),這樣就擴展了接口,這種情況下子類和父類是 is-like-a(像是一個)關係。
第二種產生差異的辦法是改變現有父類的方法的行爲(這被稱爲覆蓋 override),這種情況下子類和父類是 is-a(是一個)關係。替代原則適用於這種情況。

從這裏,我們也可以瞭解到,單說繼承關係是 is-a 的關係,是不準確的。

2.2 實現(realization)

這是類與接口之間的關係。注意類實現接口,需要用到的是 implements 關鍵字。下邊使用 Java 編程思想中的例子說明:

代碼如下:

interface Instrument {
    void play();
    String what();
    void adjust();
}

class Wind implements Instrument {
    @Override
    public void play() {
        System.out.println("Wind " + "play()");
    }
    @Override
    public String what() {
        return "Wind what()";
    }
    @Override
    public void adjust() {
        System.out.println("Wind " + "adjust()");
    }
}

class Percussion implements Instrument {
    @Override
    public void play() {
        System.out.println("Percussion " + "play()");
    }
    @Override
    public String what() {
        return "Percussion what()";
    }
    @Override
    public void adjust() {
        System.out.println("Percussion " + "adjust()");
    }
}

class Stringed implements Instrument {
    @Override
    public void play() {
        System.out.println("Stringed " + "play()");
    }
    @Override
    public String what() {
        return "Stringed what()";
    }
    @Override
    public void adjust() {
        System.out.println("Stringed " + "adjust()");
    }
}

對應的 UML 類圖如下:

在上圖中,WindPercussionStringed 類實現了 Instrument 接口。在圖中,使用空心三角箭頭+虛線來表示它們之間的實現關係。

到這裏,我們知道:繼承關係是空心三角箭頭+實線表示,實現關係是空心三角箭頭+虛線表示。記憶方法:繼承一般可以獲取父類的實現好的實實在在的方法,所以用實線;實現只是從父類獲取了方法的聲明,父類並沒有實現,這不實在,所以用虛線。

2.3 依賴(dependency)

依賴關係是一種使用關係。

依賴關係在代碼中如何體現呢?

比如如果在類 A 中,通過 new 關鍵字創建了另一個類 B 的實例, 那麼獨立於類 B 去使用或者測試類 A 是不行的。 這時,類 A 和類 B 就存在 dependency。 類 A 被叫做 dependant(依賴他人生活的人),類 B 被叫做 dependency(依賴)。 A dependant depends on its dependencies,在這裏, 類 A 依賴於類 B。依賴可以理解爲耦合。

查詢 dependency 的解釋是:(尤指過分或不利的)依靠,依賴。我們從解釋中,可以看到在代碼中出現依賴時是不好的,而依賴確實是有缺點的:代碼複用性差,難以測試,難以維護。

依賴的分類:類依賴,接口依賴,方法/屬性依賴,直接或間接的依賴。

在 UML 圖中,使用箭頭+虛線來表示。
在這裏插入圖片描述

2.4 關聯(association)

表示兩個不相關的對象之間的關係。兩個單獨的類通過它們的對象發生關聯。兩個類是不相關的,也就是說一個類可以單獨存在在另外一個類不存在的情況下。可以是一對一、一對多、多對一、一對一的關係。

在 UML 圖中,使用箭頭加實線來表示。

2.5 聚合(aggregation)

聚合是關聯的一種。如果組合是動態發生的,通常被稱爲聚合(aggregation)。

聚合是一種弱的“擁有”關係,稱爲“has-a” 關係,它體現的是 A 對象包含 B 對象,但 B 對象並不是 A 對象的一部分。比如車隊裏包含有多個車,人羣裏包含有多個人,雁羣裏包含多隻大雁。它只是一種單方向的關係,比如車隊包含車,但車並不包含車隊。


聚合關係使用空心菱形+實線表示。

2.6 組合(composition)

使用現有的類合成新的類,這就是組合(composition)。

組合經常被視爲“contains-a”(擁有)關係,組合是一種強的“擁有”關係,體現的是嚴格的部分和整體的關係,部分和整體的生命週期是一樣的(整體離不開部分,部分也離不開整體)。比如汽車擁有引擎,人擁有腿。在 UML 圖中,使用實心菱形+實線來表示組合關係。

組合非常直觀,只需要在新類中產生現有類的對象,這樣新的類就由現有類的對象所組成。組合只是複用了現有代碼的功能,而不是現有代碼的形式。

使用代碼表示如下:

public class Car {
    private Engine mEngine;
}

class Engine {
}

對應的 UML 類圖:

上圖中的數字表示 1 個 Car 對應着 1 個Engine

public class Person {
    private Leg mLeg;
}

class Leg {
}

對應的 UML 圖:

上圖中的數字表示 1 個 Person 對應着 2 個Leg

3. 最後

簡單地介紹了一下類與類之間的 6 種關係,希望能夠幫助到大家。

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