這個問題的本質是若將垃圾丟進單個垃圾筒,事實上是未經分類的。但在以後,某些特殊的信息必須恢復,以便對垃圾正確地歸類。在最開始的解決方案中,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語句中查找每一種類型,那麼很可能錯過一個重點,使最終的代碼很難維護。在下一節中,大家會學習如何逐步對這個程序進行改進,使其顯得越來越靈活。這是在程序設計中一種非常有意義的例子。