3.4 Java中繼承和組合

這篇文章闡明了Java中繼承和組合的概念。首先展示了繼承的例子,接著顯示如何通過(guò)組合來(lái)改進(jìn)繼承。最后總結(jié)如何在它們之間做選擇。

1. 繼承

想下,我們有個(gè)叫 insert的類(lèi),這個(gè)類(lèi)包含兩個(gè)方法:1) move() 和2)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");
    }
}

現(xiàn)在你需要定義一個(gè)Bee類(lèi),它是Insert類(lèi)子類(lèi),但是有attack()move()不同實(shí)現(xiàn)。它可以按照繼承如下設(shè)計(jì):

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();
    }
}

類(lèi)型繼承關(guān)系圖簡(jiǎn)單如下:

類(lèi)繼續(xù)圖

輸出:

Fly
Fly
Attack

"Fly" 被打印兩次,它顯示了move()被調(diào)用了兩次。但是它應(yīng)該只被調(diào)用一次。

問(wèn)題是由super.attack()方法導(dǎo)致。Insertattack()方法調(diào)用了move()方法。當(dāng)子類(lèi)調(diào)用super.attack(),它也調(diào)用了被覆蓋的move()方法。

為了解決這個(gè)問(wèn)題,我們可以:
1、除去子類(lèi)的attack方法。它將會(huì)使子類(lèi)完全依賴(lài)父類(lèi)實(shí)現(xiàn)的attack()方法。如果父類(lèi)的attack方法在后面被改變了(這不是你所控制的),例如,父類(lèi)的attack()方法調(diào)用另外的方法去移動(dòng),子類(lèi)也需要改變。這是個(gè)失敗的封裝。

2.如下重寫(xiě)attack()方法。

public void attack() {
    move();
    System.out.println("Attack");
}

因?yàn)樽宇?lèi)不再依賴(lài)父類(lèi)信息,所以可以保證結(jié)果的正確性。但是,父類(lèi)的代碼是重復(fù)的。(想下 attack()方法調(diào)用了更復(fù)雜的方法二不是僅僅打印一個(gè)字符串)它對(duì)于軟件工程師重用規(guī)則來(lái)說(shuō)的時(shí)候是不被準(zhǔn)許的。

繼承設(shè)計(jì)是不好的,因?yàn)樽宇?lèi)依賴(lài)了父類(lèi)的實(shí)現(xiàn)細(xì)節(jié)。如果父類(lèi)改變了,子類(lèi)也需要改變。

2.組合

在這個(gè)例子中,可以用組合而不是繼承。讓我們先看看組合的解決方案。

attack函數(shù)被抽象作為一個(gè)接口。

interface Attack {
    public void move();
    public void attack();
}

Attack接口可以被定義為多個(gè)不同種類(lèi)的attack實(shí)現(xiàn)。

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);
    }
}

因?yàn)閍ttack函數(shù)被抽象了,Insert類(lèi)將不再與attack有任何關(guān)系。

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是一個(gè)Insert類(lèi)型,它可以attack。

// 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();
    }
}

類(lèi)結(jié)構(gòu)圖如下:


組合結(jié)構(gòu)類(lèi)圖
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. 什么使用用它們

下面兩點(diǎn)可以指導(dǎo)繼承和組合之間的選擇:

  1. 如果是IS-A關(guān)系,一個(gè)類(lèi)想要對(duì)另外一個(gè)類(lèi)暴露所有接口,繼承看起來(lái)是更好的選擇。
    2.如果是HAS-A關(guān)系,組合是更好的選擇。

總之,繼承和組合都有它們的應(yīng)用場(chǎng)景,理解它們的關(guān)系是值得的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容