用多态消灭if – else if - else
如果说泛型消灭行为相同、类型不同的分支语句,多态则消灭(父)类型相同、行为不同的分支语句。
我在1999年曾经写国庆阅兵的直升飞机编队指挥系统,其地面的核心是一个地图显示系统。地图的显示代码大致如下:
public class PlaneMap {
List<MapItem> items = new ArrayList<>();
public void show() {
for (MapItem item : items) {
if (item.type == MapItem.Types.City) {
MapItemCity theCity = (MapItemCity) item;
//.... Draw the city icon.
} else if (item.type == MapItem.Types.Road) {
MapItemRoad theRoad = (MapItemRoad) item;
//... Draw the dual polylines of the road.
} else if (item.type == MapItem.Types.River) {
MapItemRiver theRiver = (MapItemRiver) item;
//... Draw the dual polylines of the river and fill them.
}
}
}
}
各种元素的绘制方法五花八门,比如City城市需要显示一个图标Icon(不同级别的城市图标不同);道路需要显示两条(某些小路其实是一条)边缘;河流则要显示河岸并填充(但复杂的河流应该显示某些江心洲之类的额外河岸)……这个Show方法很快就变得非常巨大。
得亏当年只是做飞机的导航,因而只要一些空中导航用的核心元素即可。如果显示类似高德地图之类的汽车用地图,这个方法可以包含整个系统的一半代码都不夸张。
类似这时候就需要使用虚函数来解决这个问题了。
Java代码大致如下:
在基类中声明一个实际上没有用的show方法:
public class MapItem {
// public Types type;
// public enum Types {City, Road, River}
public void show() {
}
}
注意这个时候,type也不需要了,因为子类类型将代替这个变量进行分支判断工作。
在每个子类中挨个实现show方法,代码就是之前分支语句中的代码:
public class MapItemCity extends MapItem {
@Override
public void show() {
//.... Draw the city icon.
}
}
剩下的代码则变得极其简单:
public class PlaneMap {
List<MapItem> items = new ArrayList<>();
public void add(MapItem item) {
items.add(item);
}
public void show() {
for (MapItem item : items) {
item.show();
}
}
只要各种类型的Item(只要继承了MapItem基类即可,比如MapItemCity, MapItemRoad, MapItemRiver)用add()方法加入地图,然后只有两行代码的show()方法就会顺序调用所有Item的show()方法。
由于基类的空的show()方法被子类中的@Override方法重写了,因此实际调用的实际上是子类方法。如果有些重复代码希望在所有子类中调用,则可以写在基类的show()方法中,然后在子类的show中利用super.show()来调用。
(虚)基类 vs. 接口
假设有下面这样三个类,该如何设计继承关系呢?
人,兔子,小汽车。显然可以让人和兔子继承“动物”,而小汽车继承“交通工具”。
然而问题来了:“跑”,也就是run()方法如何实现?
人和兔子好办,让基类“动物”实现run(),然后人和兔子来@Override即可;小汽车嘛,则可以让“交通工具”来帮忙,也没问题。
但如果是这样,要想让他们被扔到同一个List中集中处理,就比较难了,因为他们不像之前的各种MapItem有共同的基类。一个技术上可行的方法是让动物和交通工具强行从一个类派生,比如“可以跑的东西RunableItem”,但很可能动物早就继承了更深的基类“生物”,而交通工具也是早已继承了“机械”,这可怎么办?
曾经有一段时间盛行对“多继承”的讨论,然而现在主流语言都没有采用这种设计,而是引入了接口。
大致实现如下:
容器类(相当于之前的飞机地图PlaneMap):
public class LetsRun {
private List<iRunable> runableList = new ArrayList<iRunable>();
public void letsRun() {
for ( iRunable runable : runableList) {
runable.run();
}
}
}
接口(相当于之前的MapItem基类):
public interface iRunable {
void run();
}
其中一个类兔子(相当于之前的城市City):
public class Rabbit implements iRunable {
@Override
public void run() {
//As a rabbit, run with 4 legs.
}
}
总结一下:
派生关系(基类-派生类)代表天然的唯一从属关系;接口实现关系(接口-实现类)代表一种能力(可能有很多能力),所以接口常常以able结尾。