目錄
1. 前言
2016 年 8 月我去面試 Android 的時候,遇到一個 Java 大牛,問過我這個問題。我確實不知道類與類之間竟然會有 6 種關係。面試後,雖然查了一下,但是依然不理解:類與類之間這 6 種關係是怎麼總結出來的。
這篇文字就用來介紹類與類之間的 6 種關係,如何使用 UML 表示類與類之間的 6 種關係,如何更好地記憶 6 種關係的表示。
2. 正文
2.1 繼承(generalization)
這個大家都不陌生,可以說是非常熟悉了。使用繼承,我們能夠以現有類爲基礎(獲取它的全部可繼承的成員),通過添加或修改一些成員來創建新類。源類常被稱爲基類、超類或者父類,導出類常被稱爲繼承類或者子類。以經典的幾何形的例子來說明:
在上面的圖中,Shape
類是父類,而 Circle
、Square
、Triangle
是三個子類。我們使用空心三角箭頭+實線,來表示子類與父類之間的繼承關係。需要注意的是,箭頭是由子類指向父類的。
通過繼承,子類獲得了父類的全部可繼承的成員;但是,子類難道僅僅是爲了獲取父類的全部可繼承的成員而已嗎?
顯然不是,如果僅僅那樣,何不直接使用父類呢?
實際上,子類通常會和父類產生差異,有兩種辦法可以實現。
第一種產生差異的辦法是直接在子類中添加新的方法(這些新方法並不是父類的方法),這樣就擴展了接口,這種情況下子類和父類是 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 類圖如下:
在上圖中,Wind
、Percussion
、Stringed
類實現了 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 種關係,希望能夠幫助到大家。