本文闡述了 Java 中繼承和組合的概念。第一個範例示範了繼承,然後展示瞭如何使用組合來改進繼承設計。我們將在最後總結如何在它們之間進行選擇。
1. 繼承
假設我們有一個類別Insect
(英語昆蟲) 這類包含兩個方法:1. move()
(來自英語 move)和 2. attack()
(來自英語 Attack)
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(); //assuming an insect needs to move before attacking
System.out.println("Attack");
}
}
現在您想要定義一個類別Bee
(英國蜜蜂),它是 類型之一Insect
,但具有attack()
和 的不同實現move()
。這可以使用繼承來完成:
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();
}
}
public class InheritanceVSComposition {
public static void main(String[] args) {
Insect i = new Bee(1, "red");
i.attack();
}
}
類別層次結構圖很簡單: 執行結果:
Fly
Fly
Attack
「Fly」被輸入兩次,因此該方法move()
被呼叫兩次。但它應該只被調用一次。該問題是由 引起的super.attack()
。該方法attack ()
呼叫move()
類別方法Insect
。當子類別呼叫時super.attack ()
,它也會呼叫重寫的方法move()
。為了解決這個問題,我們可以:
- 消除
attack()
子類別方法。這將使子類別依賴attack()
超類別的方法實作。如果attack()attack()
超類別開始使用不同的移動方法,則子類別也需要變更。這是不好的封裝。 -
重寫方法
attack()
如下:public void attack() { move(); System.out.println("Attack"); }
-
這保證了正確的結果,因為子類別不再依賴超類別。然而,該程式碼是超類別的重複。(該方法
attack()
所做的事情比僅僅輸出字串更複雜)。這不是好的軟體設計,不應該有重複的程式碼。
2. 組成
您可以使用組合而不是繼承。讓我們來看看使用它的解決方案。函數attack()
被抽象化為介面。
interface Attack {
public void move();
public void attack();
}
可以透過實作Attack介面來定義不同類型的攻擊。
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);
}
}
由於攻擊函數是外部的,因此該類別Insect
不再包含它。
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;
}
}
Class Bee
(來自英語Bee),該類型如何Insect
攻擊。
// This wrapper class wrap an Attack object
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();
}
}
類別圖:
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();
}
}
執行結果:
fly
move
fly
sting
3. 何時使用這些方法?
以下兩點可以幫助您在繼承和組合之間做出選擇:- 如果您正在處理“IS”形式的類別之間的關係,並且一個類別想要向另一個類別提供其所有接口,那麼繼承是更好的選擇。
- 如果關係是“HAS”,則首選組合。
- 布洛赫、約書亞. 有效的java。印度培生教育,2008 年
- https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance
- https://www.javaworld.com/article/2076814/core-java/inheritance-versus-composition--which-one-should...
GO TO FULL VERSION