類之間的關系大體上存在五種:繼承(實作)、依賴、關聯、聚合、組合。
這其中聚合群組合都是關聯的一種特列。
繼承:對于類來說,這種關系叫做繼承,而對于接口來說,這種關系叫做實作。繼承是一種“is-a”關系。
依賴:簡單的了解,就是一個類A中的方法使用到了另一個類B。這種使用關系是具有偶然性的、臨時性的、非常弱的,但是B類的變化會影響到類A。
比如說,我想打籃球,首先需要一個類來代表我自己,然後需要一個類來代表一一個籃球,最後,‘我’要調用‘籃球’裡的方法來玩耍,用代碼實作一下:
public class Ball {
public void play(){
System.out.println("use ball to play");
}
}
public class Me {
public void play(Ball ball){//這裡,play作為Me類方法的參數。 Me類依賴Ball類
ball.play();
}
}
這就是一種類與類之間的關系,叫做依賴。
一般而言,依賴關系在Java中展現為局域變量、方法的形參,或者對靜态方法的調用。
關聯:被關聯的類以類屬性的形式出現在關聯的類中,或者關聯類A引用了一個類型為被關聯類B的全局變量的這種關系,就叫關聯關系。
展現的是兩個類、或者類與接口之間語義級别的一種強依賴關系。這種關系比依賴更強、不存在依賴關系的偶然性、關系也不是臨時性的,一般是長期性的,而且雙方的關系一般是平等的、關聯可以是單向、雙向的。
// Ball還是上面的Ball
public class You {
private Ball ball; // 讓Ball成為You的類屬性,這就是關聯
public You(Ball ball){
this.ball = ball;
}
public void play(){
ball.play();
}
}
在Java中,關聯關系一般使用成員變量來實作。
聚合:是關聯關系的一種特例,他展現的是整體與部分、擁有的關系,整體與部分是可分的,即has-a的關系
public class Family {
private List<Child> children; //一個家庭裡有許多孩子
// ...
}
在代碼層面,聚合和關聯關系是一緻的,隻能從語義級别來區分。普通的關聯關系中,a類和b類沒有必然的聯系,而聚合中,需要b類是a類的一部分,是一種”has-a“的關系,即 a has-a b; 比如家庭有孩子,屋子裡有空調。但是,has 不是 must has,a可以有b,也可以沒有。
不同于關聯關系的平等地位,聚合關系中兩個類的地位是不平等。
組合:是關聯關系的一種特例,他展現的是一種contains-a的關系,整體與部分是不可分的,關系比聚合更強,也稱為強聚合。
public class Person {
private Eye eye = new Eye(); //一個人有鼻子有眼睛
// ....
}
組合同樣展現整體與部分間的關系,但此時整體與部分是不可分的,整體的生命周期結束也就意味着部分的生命周期結束。
同樣,組合關系中,兩個類關系也是不平等的。
幾種關系所表現的強弱程度依次為:組合>聚合>關聯>依賴;
總結
學過設計模式的都知道,要“少用繼承,多用組合”,這究竟是為什麼呢?
組合與繼承的差別和聯系
在繼承結構中,父類的内部細節對于子類是可見的。是以我們通常也可以說通過繼承的代碼複用是一種 白盒式代碼複用。(如果基類的實作發生改變,那麼派生類的實作也将随之改變。這樣就導緻了子類行為的不可預知性)
組合是通過對現有的對象進行拼裝(組合)産生新的、更複雜的功能。因為在對象之間,各自的内部細節是不可見的,是以我們也說這種方式的代碼複用是黑盒式代碼複用 。(因為組合中一般都定義一個類型,是以在編譯期根本不知道具體會調用哪個實作類的方法)
繼承在寫代碼的時候就要指名具體繼承哪個類,是以,在編譯期就确定了關系。
組合,在寫代碼的時候可以采用面向接口程式設計。是以,類的組合關系一般在運作期确定。
組合是在組合類和被包含類之間的一種松耦合關系,而繼承則是父類和子類之間的一種緊耦合關系。
當選擇使用組合關系時,在組合類中包含了外部類的對象,組合類可以調用外部類必須的方法,而使用繼承關系時,父類的所有方法和變量都被子類無條件繼承,子類不能選擇。
引用網友的一句很經典的話應該更能讓大家厘清繼承群組合的差別:組合可以被說成“我請了個老頭在我家裡幹活” ,繼承則是“我父親在家裡幫我幹活”。從這句話可以看出組合的靈活性比繼承高,這應該是“少用繼承,多用組合”的原因吧。
是以建議在同樣可行的情況下,優先使用組合而不是繼承。