繼承共同實作


繼承基本上就是避免多個類別間重複定義了相同的實作。以實際的例子來說明比較清楚,假設你在正開發一款RPG(Role-playing game)遊戲,一開始設定的角色有劍士與魔法師。首先你定義了劍士類別:

public class SwordsMan {
    private String name;   // 角色名稱
    private int level;     // 角色等級
    private int blood;     // 角色血量

    public void fight() {
        System.out.println("揮劍攻擊");
    }

    public int getBlood() {
        return blood;
    }
    public void setBlood(int blood) {
        this.blood = blood;
    }

    public int getLevel() {
        return level;
    }
    public void setLevel(int level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

接著你為魔法師定義類別:

public class Magician {
    private String name;   // 角色名稱
    private int level;     // 角色等級
    private int blood;     // 角色血量

   
    public void fight() {
        System.out.println("魔法攻擊");
    }
   
    public void cure() {
        System.out.println("魔法治療");
    }

    public int getBlood() {
        return blood;
    }
    public void setBlood(int blood) {
        this.blood = blood;
    }

    public int getLevel() {
        return level;
    }
    public void setLevel(int level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }

}


你注意到什麼呢?因為只要是遊戲中的角色,都會具有角色名稱、等級與血量,類別中也都為名稱、等級與血量定義了取值方法與設值方法,Magician中粗體字部份與SwordsMan中相對應的程式碼重複了。重複在程式設計上,就是不好的訊號。舉個例子來說,如果你要將namelevelblood改名為其他名稱,那就要修改SwordsManMagician兩個類別,如果有更多類別具有重複的程式碼,那就要修改更多類別,造成維護上的不便。

如果要改進,可以把相同的程式碼提昇(Pull up)為父類別:

package cc.openhome;

public class Role {
    private String name;
    private int level;
    private int blood;
    
    public int getBlood() {
        return blood;
    }

    public void setBlood(int blood) {
        this.blood = blood;
    }

    public int getLevel() {
        return level;
    }

    public void setLevel(int level) {
        this.level = level;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

這個類別在定義上沒什麼特別的新語法,只不過是將SwordsManMagician中重複的程式碼複製過來。接著SwordsMan可以如下繼承Role

package cc.openhome;

public class SwordsMan extends Role {
    public void fight() {
        System.out.println("揮劍攻擊");
    }
}

在這邊看到了新的關鍵字extends,這表示SwordsMan會擴充Role的實作,也就是繼承Role的實作,再擴充Role原本沒有的fight()實作。程式面上來說,Role中有定義的程式碼,SwordsMan中都繼承而擁有了,並再定義了fight()方法的程式碼。類似地,Magician也可以如下定義繼承Role類別:

package cc.openhome;

public class Magician extends Role { public void fight() { System.out.println("魔法攻擊"); } public void cure() { System.out.println("魔法治療"); } }

Magician繼承Role的實作,再擴充了Role原本沒有的fight()cure()實作。

如何看出確實有繼承了呢?以下簡單的程式可以看出:

package cc.openhome;

public class RPG {
    public static void main(String[] args) {
        demoSwordsMan();
        demoMagician();
    }

    static void demoSwordsMan() {
        SwordsMan swordsMan = new SwordsMan();
        swordsMan.setName("Justin");
        swordsMan.setLevel(1);
        swordsMan.setBlood(200);
        System.out.printf("劍士:(%s, %d, %d)%n", swordsMan.getName(), 
                swordsMan.getLevel(), swordsMan.getBlood());
    }

    static void demoMagician() {
        Magician magician = new Magician();
        magician.setName("Monica");
        magician.setLevel(1);
        magician.setBlood(100);
        System.out.printf("魔法師:(%s, %d, %d)%n", magician.getName(), 
                magician.getLevel(), magician.getBlood());
    }
}

雖然SwordsManMagician並沒有定義getName()getLevel()getBlood()等方法,但從Role繼承了這些方法,所以就如範例中可以直接使用,執行的結果如下:

劍士:(Justin, 1, 200)
魔法師:(Monica, 1, 100)

繼承的好處之一,就是若你要將namelevelblood改名為其它名稱,那就只要修改Role.java就可以了,只要是繼承Role的子類別都無需修改。有的書籍或文件會說,private成員無法繼承,那是錯的!如果private成員無法繼承,那為什麼上面的範例namelevelblood記錄的值會顯示出來呢?private成員會被繼承,只不過子類別無法直接存取,必須透過父類別提供的方法來存取(如果父類別願意提供存取方法的話)。