16.8 RTTI真的有害吗.md 5.5 KB
Newer Older
Q
quanke 已提交
1 2
# 16.8 RTTI真的有害吗

Q
quanke 已提交
3

W
ch16  
wizardforcel 已提交
4
本章的各种设计模式都在努力避免使用RTTI,这或许会给大家留下“RTTI有害”的印象(还记得可怜的`goto`吗,由于给人印象不佳,根本就没有放到Java里来)。但实际情况并非绝对如此。正确地说,应该是RTTI使用不当才“有害”。我们之所以想避免RTTI的使用,是由于它的错误运用会造成扩展性受到损害。而我们事前提出的目标就是能向系统自由加入新类型,同时保证对周围的代码造成尽可能小的影响。由于RTTI常被滥用(让它查找系统中的每一种类型),会造成代码的扩展能力大打折扣——添加一种新类型时,必须找出使用了RTTI的所有代码。即使仅遗漏了其中的一个,也不能从编译器那里得到任何帮助。
Q
quanke 已提交
5

W
ch16  
wizardforcel 已提交
6
然而,RTTI本身并不会自动产生非扩展性的代码。让我们再来看一看前面提到的垃圾回收例子。这一次准备引入一种新工具,我把它叫作`TypeMap`。其中包含了一个`Hashtable`(散列表),其中容纳了多个`Vector`,但接口非常简单:可以添加(`add()`)一个新对象,可以获得(`get()`)一个`Vector`,其中包含了属于某种特定类型的所有对象。对于这个包含的散列表,它的关键在于对应的`Vector`里的类型。这种设计模式的优点(根据Larry O'Brien的建议)是在遇到一种新类型的时候,`TypeMap`会动态加入一种新类型。所以不管什么时候,只要将一种新类型加入系统(即使在运行期间添加),它也会正确无误地得以接受。
Q
quanke 已提交
7

W
ch16  
wizardforcel 已提交
8
我们的例子同样建立在`c16.Trash`这个“包”(`Package`)内的`Trash`类型结构的基础上(而且那儿使用的`Trash.dat`文件可以照搬到这里来)。
Q
quanke 已提交
9

Q
quanke 已提交
10
```
W
ch16  
wizardforcel 已提交
11
//: DynaTrash.java
Q
quanke 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
// Using a Hashtable of Vectors and RTTI
// to automatically sort trash into
// vectors. This solution, despite the
// use of RTTI, is extensible.
package c16.dynatrash;
import c16.trash.*;
import java.util.*;

// Generic TypeMap works in any situation:
class TypeMap {
  private Hashtable t = new Hashtable();
  public void add(Object o) {
    Class type = o.getClass();
    if(t.containsKey(type))
      ((Vector)t.get(type)).addElement(o);
    else {
      Vector v = new Vector();
      v.addElement(o);
      t.put(type,v);
    }
  }
  public Vector get(Class type) {
    return (Vector)t.get(type);
  }
  public Enumeration keys() { return t.keys(); }
  // Returns handle to adapter class to allow
  // callbacks from ParseTrash.fillBin():
W
ch16  
wizardforcel 已提交
39
  public Fillable filler() {
Q
quanke 已提交
40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
    // Anonymous inner class:
    return new Fillable() {
      public void addTrash(Trash t) { add(t); }
    };
  }
}

public class DynaTrash {
  public static void main(String[] args) {
    TypeMap bin = new TypeMap();
    ParseTrash.fillBin("Trash.dat",bin.filler());
    Enumeration keys = bin.keys();
    while(keys.hasMoreElements())
      Trash.sumValue(
        bin.get((Class)keys.nextElement()));
  }
} ///:~
Q
quanke 已提交
57
```
Q
quanke 已提交
58

W
ch16  
wizardforcel 已提交
59
尽管功能很强,但对`TypeMap`的定义是非常简单的。它只是包含了一个散列表,同时`add()`负担了大部分的工作。添加一个新类型时,那种类型的`Class`对象的引用会被提取出来。随后,利用这个引用判断容纳了那类对象的一个`Vector`是否已存在于散列表中。如答案是肯定的,就提取出那个`Vector`,并将对象加入其中;反之,就将`Class`对象及新`Vector`作为一个“键-值”对加入。
W
wizardforcel 已提交
60

W
ch16  
wizardforcel 已提交
61
利用`keys()`,可以得到对所有`Class`对象的一个“枚举”(`Enumeration`),而且可用`get()`,可通过`Class`对象获取对应的`Vector`
Q
quanke 已提交
62

W
ch16  
wizardforcel 已提交
63
`filler()`方法非常有趣,因为它利用了`ParseTrash.fillBin()`的设计——不仅能尝试填充一个`Vector`,也能用它的`addTrash()`方法试着填充实现了`Fillable`(可填充)接口的任何东西。`filter()`需要做的全部事情就是将一个引用返回给实现了`Fillable`的一个接口,然后将这个引用作为参数传递给`fillBin()`,就象下面这样:
Q
quanke 已提交
64 65

```
Q
quanke 已提交
66
ParseTrash.fillBin("Trash.dat", bin.filler());
Q
quanke 已提交
67 68
```

W
ch16  
wizardforcel 已提交
69
为产生这个引用,我们采用了一个“匿名内部类”(已在第7章讲述)。由于根本不需要用一个已命名的类来实现`Fillable`,只需要属于那个类的一个对象的引用即可,所以这里使用匿名内部类是非常恰当的。
Q
quanke 已提交
70

W
ch16  
wizardforcel 已提交
71
对这个设计,要注意的一个地方是尽管没有设计成对归类加以控制,但在`fillBin()`每次进行归类的时候,都会将一个`Trash`对象插入`bin`
Q
quanke 已提交
72

W
ch16  
wizardforcel 已提交
73
通过前面那些例子的学习,`DynaTrash`类的大多数部分都应当非常熟悉了。这一次,我们不再将新的`Trash`对象置入类型`Vector`的一个`bin`内。由于`bin`的类型为`TypeMap`,所以将垃圾(`Trash`)丢进垃圾筒(`Bin`)的时候,`TypeMap`的内部归类机制会立即进行适当的分类。在`TypeMap`里遍历并对每个独立的`Vector`进行操作,这是一件相当简单的事情:
Q
quanke 已提交
74

Q
quanke 已提交
75 76

```
Q
quanke 已提交
77 78 79 80
    Enumeration keys = bin.keys();
    while(keys.hasMoreElements())
      Trash.sumValue(
        bin.get((Class)keys.nextElement()));
Q
quanke 已提交
81
```
Q
quanke 已提交
82

W
ch16  
wizardforcel 已提交
83
就象大家看到的那样,新类型向系统的加入根本不会影响到这些代码,亦不会影响`TypeMap`中的代码。这显然是解决问题最圆满的方案。尽管它确实严重依赖RTTI,但请注意散列表中的每个键-值对都只查找一种类型。除此以外,在我们增加一种新类型的时候,不会陷入“忘记”向系统加入正确代码的尴尬境地,因为根本就没有什么代码需要添加。