Java中的继承与组合
本文主要說明Java中繼承與組合的概念,以及它們之間的聯(lián)系與區(qū)別。首先文章會給出一小段代碼示例,用于展示到底什么是繼承。然后演示如何通過“組合”來改進(jìn)這種繼承的設(shè)計機(jī)制。最后總結(jié)這兩者的應(yīng)用場景,即到底應(yīng)該選擇繼承還是組合。
1、繼承
假設(shè)我們有一個名為Insect(昆蟲)的類,這個類包含兩個方法:1)移動move(); 2)攻擊attack()。
代碼如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | class Insect { ????private int size; ????private String color; ????public Insect(int size, String color) { ????????this.size = size; ????????this.color = color; ????} ????public int getSize() { ????????return size; ????} ????public void setSize(int size) { ????????this.size = size; ????} ????public String getColor() { ????????return color; ????} ????public void setColor(String color) { ????????this.color = color; ????} ????public void move() { ????????System.out.println("Move"); ????} ????public void attack() { ????????move();? //假設(shè)昆蟲在攻擊前必須要先移動一次 ????????System.out.println("Attack"); ????} } |
現(xiàn)在,你想要定義一個名為Bee(蜜蜂)的類。Bee(蜜蜂)是Insect(昆蟲)的一種,但實現(xiàn)了不同于Insect(昆蟲)的attack()和move方法。這時候我們可以用繼承的設(shè)計機(jī)制來實現(xiàn)Bee類,就像下面的代碼一樣:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class Bee extends Insect { ????public Bee(int size, String color) { ????????super(size, color); ????} ????public void move() { ????????System.out.println("Fly"); ????} ????public void attack() { ????????move(); ????????super.attack(); ????} } |
| 1 2 3 4 5 6 | public class InheritanceVSComposition { ????public static void main(String[] args) { ????????Insect i = new Bee(1, "red"); ????????i.attack(); ????} } |
InheritanceVSComposition作為一個測試類,在其main方法中生成了一個Bee類的實例,并賦值給Insect類型的引用變量 i。所以調(diào)用i的attack方法時,對應(yīng)的是Bee類實例的attack方法,也就是調(diào)用了Bee類的attack方法。
類的繼承結(jié)構(gòu)圖如下,非常簡單:
輸出:
| 1 2 3 | Fly Fly Attack |
Fly被打印了兩次,也就是說move方法被調(diào)用了兩次。但按理來講,move方法只應(yīng)當(dāng)被調(diào)用一次,因為無論是昆蟲還是蜜蜂,一次攻擊前只移動一次。
問題出在子類(即Bee類)的attack方法的重載代碼中,也就是super.attack()這一句。因為在父類(即Insect類)中,調(diào)用 attack方法時會先調(diào)用move方法,所以當(dāng)子類(Bee)調(diào)用super.attack()時,相當(dāng)于也同時調(diào)用了被重載的move方法(注意是子 類被重載的move方法,而不是父類的move方法)。
為了解決這個問題,我們可以采取以下辦法:
| 1 2 3 4 | public void attack() { ????move(); ????System.out.println("Attack"); } |
這樣保證了結(jié)果的正確性,因為子類的attack方法不再依賴于父類。但是,子類attack方法的代碼與父類產(chǎn)生了重復(fù)(重復(fù)的attack方法會使得很多事情變得復(fù)雜,不僅僅是多打印了一條輸出語句)。所以第二種辦法也不行,它不符合軟件工程中關(guān)于重用的思想。
如此看來,繼承機(jī)制是有缺點的:子類依賴于父類的實現(xiàn)細(xì)節(jié),如果父類產(chǎn)生了變更,子類的后果將不堪設(shè)想。
2、組合
在上面的例子中,可以用組合的機(jī)制來替代繼承。我們先看一下運用組合如何實現(xiàn)。
attack這一功能不再是一個方法,而是被抽象為一個接口。
| 1 2 3 4 | interface Attack { ????public void move(); ????public void attack(); } |
通過對Attack接口的實現(xiàn),就可以在實現(xiàn)類當(dāng)中定義不同類型的attack。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | class AttackImpl implements Attack { ????private String move; ????private String attack; ????public AttackImpl(String move, String attack) { ????????this.move = move; ????????this.attack = attack; ????} ????@Override ????public void move() { ????????System.out.println(move); ????} ????@Override ????public void attack() { ????????move(); ????????System.out.println(attack); ????} } |
因為attack功能已經(jīng)被抽象為一個接口,所以Insect類不再需要有attack方法。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class Insect { ????private int size; ????private String color; ????public Insect(int size, String color) { ????????this.size = size; ????????this.color = color; ????} ????public int getSize() { ????????return size; ????} ????public void setSize(int size) { ????????this.size = size; ????} ????public String getColor() { ????????return color; ????} ????public void setColor(String color) { ????????this.color = color; ????} } |
Bee類一種Insect類,它具有attack的功能,所以它實現(xiàn)了attack接口:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 這個封裝類封裝了一個Attack類型的對象 class Bee extends Insect implements Attack { ????private Attack attack; ????public Bee(int size, String color, Attack attack) { ????????super(size, color); ????????this.attack = attack; ????} ????public void move() { ????????attack.move(); ????} ????public void attack() { ????????attack.attack(); ????} } |
類圖:
測試類代碼,將AttackImpl的實例作為Attack類型的參數(shù)傳給Bee類的構(gòu)造函數(shù):
| 1 2 3 4 5 6 7 8 9 10 11 12 | public class InheritanceVSComposition2 { ????public static void main(String[] args) { ????????Bee a = new Bee(1, "black", new AttackImpl("fly", "move")); ????????a.attack(); ????????// if you need another implementation of move() ????????// there is no need to change Insect, we can quickly use new method to attack ????????Bee b = new Bee(1, "black", new AttackImpl("fly", "sting")); ????????b.attack(); ????} } |
| 1 2 3 4 | fly move fly sting |
3、什么時候該用繼承,什么時候該用組合?
以下兩條原則說明了應(yīng)該如何選擇繼承與組合:
- 如果存在一種IS-A的關(guān)系(比如Bee“是一個”Insect),并且一個類需要向另一個類暴露所有的方法接口,那么更應(yīng)該用繼承的機(jī)制。
- 如果存在一種HAS-A的關(guān)系(比如Bee“有一個”attack功能),那么更應(yīng)該運用組合。
總結(jié)來說,繼承和組合都有他們的用處。只有充分理解各對象和功能之間的關(guān)系,才能充分發(fā)揮這兩種機(jī)制各自的優(yōu)點。
參考:
總結(jié)
以上是生活随笔為你收集整理的Java中的继承与组合的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我必须要吐槽,你们的数据管理都是错的,这
- 下一篇: 有个程序媛女朋友是一种什么样的感觉?