继承与多态
规划程序之前要考虑到未来,及时做好应对需求变更的准备。
良好的面向对象程序设计需要尽量提高程序的扩展性和灵活性,当需求变更或加入新的类型时不至于重写设计或改写太多既有代码。众所周知,继承是面向对象程序设计三大基本特性(封装、继承、多态)之一,充分理解继承的原理机制,对于书写高质量的程序至关重要。
面向对象程序设计显著特点就是尽量少写重复代码,多考虑既有代码的重构,以此提高程序的扩展性。为什么需要继承机制?说白了就是为了减少重复代码。在设计继承时,我们总会从一系列具有相同属性和行为的类中抽象出共有的部分,然后封装在父类中,以Java方式来说,“子类继承父类”意味着子类拥有父类的实例变量和方法,这部分共有的属性和行为无需重新编写,直接继承即可;当然,子类也可以添加自己特有的实例变量和方法,利用继承机制可以大大程序设计的灵活性。
以上叙述了这么多,但好像太理论了,完全没有体现出继承的特点和优势。别着急,考虑这样一个案例:
“假设你需要设计一套动物仿真系统,可以使用户将一群动物丢至特定的环境,并因此模拟出不同动物的状态和行为。现已存在部分被告知的动物的属性和行为,但并不清楚以后会有多少动物被加进来。你需要设计扩展性良好的应用程序,以此应对后期动物园管理员以任意时间引进任意数量动物的需求。”
好吧,话有点啰嗦,还是直接开始设计吧。
(1)找出类间具有共同属性和行为的对象,并抽象出共有部分。
假设你的动物仿真系统需要存储动物的图像(picture)、食物(food)、饥饿程度(hunger)、活动范围的长和宽(boundaries)、活动过程中的X、Y坐标(location)、另外但凡动物均有发声(makeNoise)、饮食(eat)、睡眠(Sleep)、闲逛(roam)的行为。
假定现在的动物系统中有狮子(Lion)、老虎(Tiger)、猫咪(Cat)、河马(Hippo)、狼(Wolf)、狗(Dog)
(2)抽象出代表共同状态和行为的类
经以上分析我们抽象出动物(Animal)作为整个系统的顶层父类,并将共有的图像(picture)、食物(food)、饥饿程度(hunger)、活动范围的长和宽(boundaries)、活动过程中的X、Y坐标(location)实例变量和发声(makeNoise)、饮食(eat)、睡眠(Sleep)、闲逛(roam)方法加入Animal这个类中,并利用IDEA自动生成了Get和Set方法。
public class Animal { String picture;//动物图像的存储路径 String food;//目前只有meat和grass两种 Integer hunger;//饥饿程度 Double boundaries;//活动区域面积 Location location = new Location(); //活动坐标 public void makeNoise() {//发声 System.out.println("makeNoise method is invoked in Animal...."); } public void eat() { //饮食 System.out.println("eat method is invoked in Animal...."); } public void sleep() {//睡眠 System.out.println("sleep method is invoked in Animal...."); } public void roam() { //闲逛 System.out.println("roam method is invoked in Animal...."); } public String getPicture() { return picture; } public void setPicture(String picture) { this.picture = picture; } public String getFood() { return food; } public void setFood(String food) { this.food = food; } public Integer getHunger() { return hunger; } public void setHunger(Integer hunger) { this.hunger = hunger; } public Double getBoundaries() { return boundaries; } public void setBoundaries(Double boundaries) { this.boundaries = boundaries; } public Location getLocation() { return location; } public void setLocation(Location location) { this.location = location; } }
(3)确定子类是否需要重写方法和添加自己的实例变量
观察Animal,容易发现每个动物的eat()方法和makeNoise()方法的方式应该不一样,当然睡眠也可以不同,但为使得描述问题的简单,我们在此假定睡眠一样。以makeNoise()为例,狗狗(Dog)的发声是“汪汪汪”、猫咪(Cat)的发声是“喵喵喵”等等。。根据实际情况,每个子类都可以重写(overload)继承自父类的方法以满足自己的需求。例如Dog子类。
public class Dog extends Animal{ public void makeNoise() { System.out.println("大家好,我是旺财!,我喜欢旺旺旺..."); } public void eat() { System.out.println("大家好,我是旺财!,我喜欢鲜美的骨头..."); } }
当然其他的动物也可以根据自己的特性重写继承自父类的方法,或是添加实例变量,在此不再一一列出。
(4)寻找使用共同行为的子类来构造出更多的抽象化机会
现实世界中,动物本身具有组织化的层次(界、门、纲、目、科、属、种),据此,我们可以抽象出有意义的类。观察发现,现有动物中可以抽象出Canine(猫科)、Feline(犬科)两个科,其中狮子(Lion)、老虎(Tiger)、猫咪(Cat)属于Canine(猫科)、Hippo独立于猫科和犬科、狼(Wolf)、狗(Dog)属于Feline(犬科)。所以我们可以进一步的抽象出Canine和Feline两个类。
public class Canine extends Animal { public void roam() {//重写来自Animal的roam()方法 System.out.println("roam method is invoked in Canine...."); } }
public class Feline extends Animal { public void roam() { //重写来自Animal的roam()方法 System.out.println("roam method is invoked in Feline...."); } }
(5)完成类的继承层次
根据以上分析,我们可以完成类的继承层次,其中Animal属于顶层父类,Canine(猫科)、Hippo(河马)、Feline(犬科)分别继承自Animal;狮子(Lion)、老虎(Tiger)、猫咪(Cat)继承Canine(猫科);狼(Wolf)、狗(Dog)继承Feline(犬科),由此可得类间继承关系图
至此,动物仿真系统的所有类间关系就分析整理出来了,当然,每个子类可以任意添加自己的方法或是overload继承自父类的方法,极大的减少了重复代码的书写,并且提高了系统扩展性。