這個問題的本質是若將垃圾丟進單個垃圾筒,事實上是未經分類的。但在以後,某些特殊的信息必須恢復,以便對垃圾正確地歸類。在最開始的解決方案中,RTTI扮演了關鍵的角色(詳見第11章)。
這並不是一種普通的設計,因為它增加了一個新的限制。正是這個限制使問題變得非常有趣——它更象我們在工作中碰到的那些非常麻煩的問題。這個額外的限制是:垃圾抵達垃圾回收站時,它們全都是混合在一起的。程序必須為那些垃圾的分類定出一個模型。這正是RTTI發揮作用的地方:我們有大量不知名的垃圾,程序將正確判斷出它們所屬的類型。
//: RecycleA.java
// Recycling with RTTI
package c16.recyclea;
import java.util.*;
import java.io.*;
abstract class Trash {
private double weight;
Trash(double wt) { weight = wt; }
abstract double value();
double weight() { return weight; }
// Sums the value of Trash in a bin:
static void sumValue(Vector bin) {
Enumeration e = bin.elements();
double val = 0.0f;
while(e.hasMoreElements()) {
// One kind of RTTI:
// A dynamically-checked cast
Trash t = (Trash)e.nextElement();
// Polymorphism in action:
val += t.weight() * t.value();
System.out.println(
"weight of " +
// Using RTTI to get type
// information about the class:
t.getClass().getName() +
" = " + t.weight());
}
System.out.println("Total value = " + val);
}
}
class Aluminum extends Trash {
static double val = 1.67f;
Aluminum(double wt) { super(wt); }
double value() { return val; }
static void value(double newval) {
val = newval;
}
}
class Paper extends Trash {
static double val = 0.10f;
Paper(double wt) { super(wt); }
double value() { return val; }
static void value(double newval) {
val = newval;
}
}
class Glass extends Trash {
static double val = 0.23f;
Glass(double wt) { super(wt); }
double value() { return val; }
static void value(double newval) {
val = newval;
}
}
public class RecycleA {
public static void main(String[] args) {
Vector bin = new Vector();
// Fill up the Trash bin:
for(int i = 0; i < 30; i++)
switch((int)(Math.random() * 3)) {
case 0 :
bin.addElement(new
Aluminum(Math.random() * 100));
break;
case 1 :
bin.addElement(new
Paper(Math.random() * 100));
break;
case 2 :
bin.addElement(new
Glass(Math.random() * 100));
}
Vector
glassBin = new Vector(),
paperBin = new Vector(),
alBin = new Vector();
Enumeration sorter = bin.elements();
// Sort the Trash:
while(sorter.hasMoreElements()) {
Object t = sorter.nextElement();
// RTTI to show class membership:
if(t instanceof Aluminum)
alBin.addElement(t);
if(t instanceof Paper)
paperBin.addElement(t);
if(t instanceof Glass)
glassBin.addElement(t);
}
Trash.sumValue(alBin);
Trash.sumValue(paperBin);
Trash.sumValue(glassBin);
Trash.sumValue(bin);
}
} ///:~
要注意的第一個地方是package語句:
package c16.recyclea;
這意味著在本書采用的源碼目錄中,這個文件會被置入從c16(代表第16章的程序)分支出來的recyclea子目錄中。第17章的解包工具會負責將其置入正確的子目錄。之所以要這樣做,是因為本章會多次改寫這個特定的例子;它的每個版本都會置入自己的“包”(package)內,避免類名的沖突。
其中創建了幾個Vector對象,用於容納Trash句柄。當然,Vector實際容納的是Object(對象),所以它們最終能夠容納任何東西。之所以要它們容納Trash(或者從Trash衍生出來的其他東西),唯一的理由是我們需要謹慎地避免放入除Trash以外的其他任何東西。如果真的把某些“錯誤”的東西置入Vector,那麼不會在編譯期得到出錯或警告提示——只能通過運行期的一個違例知道自己已經犯了錯誤。
Trash句柄加入後,它們會丟失自己的特定標識信息,只會成為簡單的Object句柄(上溯造型)。然而,由於存在多形性的因素,所以在我們通過Enumeration sorter調用動態綁定方法時,一旦結果Object已經造型回Trash,仍然會發生正確的行為。sumValue()也用一個Enumeration對Vector中的每個對象進行操作。
表面上持,先把Trash的類型上溯造型到一個集合容納基礎類型的句柄,再回過頭重新下溯造型,這似乎是一種非常愚蠢的做法。為什麼不只是一開始就將垃圾置入適當的容器裡呢?(事實上,這正是撥開“回收”一團迷霧的關鍵)。在這個程序中,我們很容易就可以換成這種做法,但在某些情況下,系統的結構及靈活性都能從下溯造型中得到極大的好處。
該程序已滿足了設計的初衷:它能夠正常工作!只要這是個一次性的方案,就會顯得非常出色。但是,真正有用的程序應該能夠在任何時候解決問題。所以必須問自己這樣一個問題:“如果情況發生了變化,它還能工作嗎?”舉個例子來說,厚紙板現在是一種非常有價值的可回收物品,那麼如何把它集成到系統中呢(特別是程序很大很復雜的時候)?由於前面在switch語句中的類型檢查編碼可能散布於整個程序,所以每次加入一種新類型時,都必須找到所有那些編碼。若不慎遺漏一個,編譯器除了指出存在一個錯誤之外,不能再提供任何有價值的幫助。
RTTI在這裡使用不當的關鍵是“每種類型都進行了測試”。如果由於類型的子集需要特殊的對待,所以只尋找那個子集,那麼情況就會變得好一些。但假如在一個switch語句中查找每一種類型,那麼很可能錯過一個重點,使最終的代碼很難維護。在下一節中,大家會學習如何逐步對這個程序進行改進,使其顯得越來越靈活。這是在程序設計中一種非常有意義的例子。