用多态消滅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結尾。