陈旧语法密度之八——使用多态消灭if-else if-else

用多态消灭if – else if - else

如果说泛型消灭行为相同、类型不同的分支语句,多态则消灭(父)类型相同、行为不同的分支语句。

我在1999年曾经写国庆阅兵的直升飞机编队指挥系统,其地面的核心是一个地图显示系统。地图的显示代码大致如下:

 
  
public class PlaneMap {
    List 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 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 runableList = new ArrayList();

    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结尾。

还有一种通俗的表达:想用多继承的时候,就是应该用接口的时候。

后面这个是自己总结的,如果早一些看到,就不会在前三年都没用接口了。

下一讲,神秘的工厂模式将登场,来建立复杂逻辑分支的解耦。

你可能感兴趣的:(陈旧语法密度之八——使用多态消灭if-else if-else)