3.5 继承
Android游戏开发详解
在设计对象的分类的时候,你可能会发现另一种叫作继承(inheritance)的模式,它给了我们更多的控制权。继承描述了这样一种现象,一个类继承了另一个类中的变量和方法。在这种情况下,继承者称为子类(subclass,或孩子类),而祖先称作超类(superclass,或者父类)。

使用继承比使用接口的优点在于,可以具备复用代码的能力。还记得吧,实现了一个接口的每一个类,都必须针对接口中声明的每一个抽象方法提供一个完整的实现。使用前面小节的例子,这意味着,King、Villain、Professor和SushiChef,都必须拥有它们自己的eat()、walk()和urinate()方法。在这种情况下,继承很强大,因为它允许相似的类共享方法和变量。我们将使用一个假想的角色扮演游戏的例子来说明这一点。

在创建一款角色扮演游戏的时候,你可能有一个名为Hero的类来表示玩家角色,如程序清单3.10所示。

程序清单3.10 Hero类

01 public class Hero {
02    protected int health = 10; // We will discuss ‘protected’ later in this section
03    protected int power = 5; 
04    protected int armor = 3; 
05
06    public void drinkPotion(Potion p) {
07     health += p.volume(); // Equivalent to health = health + p.volume();
08    }
09    
10    public void takeDamage(int damage) {
11    int realDamage = damage - armor; 
12    if (realDamage > 0) {
13      health -= realDamage; // Equivalent to health = health – realDamage.
14    }
15    }
16    
17    // ... more methods
18
19 }

在创建了Hero之后,你随后决定要让自己的RPG和竞争者有所区分,那就实现一个独特的类系统,其中玩家能够在此前没有见过的Warrior、Mage和Rogue类之间做出选择。

接下来,和任何值得尊敬的面向对象程序员会做的一样,你为每一种角色类型创建了一个单独的Java类,因为Warrior、Mage和Rogue中的每一个都应该具有无法想象的强大而独特的能力。你还决定,既然所有的角色类都是泛型的Hero类的第一个和最重要的扩展,它们每一个都应该拥有程序清单3.10中的Hero类的所有变量和方法。这就是继承的用武之地。

来看一下程序清单3.11到程序清单3.13。

程序清单3.11 Warrior类

public class Warrior extends Hero {

    //    ... other variables and methods
    public void shieldBash() {
        ...
    }
}
程序清单3.12 Mage类

public class Mage extends Hero {

    //  ... other variables and methods
    public void useMagic() {
      ...
    }
}
程序清单3.13 Rogue类

public class Rogue extends Hero {

    //    ... other variables and methods
    public void pickPocket() {
        ...
    }
}

注意,我们使用关键字extends表示继承。这是合适的,因为所有这3个类都是超类Hero的扩展。在继承中,每个子类都针对超类中的所有非私有的变量和方法,接受它们自己的版本(程序清单3.10中的protected变量,类似于private变量,因为外部类是无法访问它们的;然而,和private变量不同,在继承中,子类是可以访问它们的)。

在应用多态的时候,继承的好处最明显,多态允许我们在如下所示的一个方法中使用Hero的任何子类。

// Will attack any Hero regardless of Class
public void attackHero(Hero h, int monsterDamage) {
      h.takeDamage(monsterDamage); 
}