返回介绍

16.6.1 实现双重派遣

发布于 2024-10-15 23:56:36 字数 7268 浏览 0 评论 0 收藏 0

记住多形性只能通过方法调用才能表现出来,所以假如想使双重派遣正确进行,必须执行两个方法调用:在每种结构中都用一个来判断其中的类型。在 Trash 结构中,将使用一个新的方法调用 addToBin(),它采用的参数是由 TypeBin 构成的一个数组。那个方法将在数组中遍历,尝试将自己加入适当的垃圾筒,这里正是双重派遣发生的地方。

新建立的分级结构是 TypeBin,其中包含了它自己的一个方法,名为 add(),而且也应用了多形性。但要注意一个新特点:add() 已进行了“过载”处理,可接受不同的垃圾类型作为参数。因此,双重满足机制的一个关键点是它也要涉及到过载。

程序的重新设计也带来了一个问题:现在的基础类 Trash 必须包含一个 addToBin() 方法。为解决这个问题,一个最直接的办法是复制所有代码,并修改基础类。然而,假如没有对源码的控制权,那么还有另一个办法可以考虑:将 addToBin() 方法置入一个接口内部,保持 Trash 不变,并继承新的、特殊的类型 Aluminum,Paper,Glass 以及 Cardboard。我们在这里准备采取后一个办法。

这个设计方案中用到的大多数类都必须设为 public(公用)属性,所以它们放置于自己的类内。下面列出接口代码:

//: TypedBinMember.java
// An interface for adding the double dispatching
// method to the trash hierarchy without 
// modifying the original hierarchy.
package c16.doubledispatch;

interface TypedBinMember {
  // The new method:
  boolean addToBin(TypedBin[] tb);
} ///:~

在 Aluminum,Paper,Glass 以及 Cardboard 每个特定的子类型内,都会实现接口 TypeBinMember 的 addToBin() 方法,但每种情况下使用的代码“似乎”都是完全一样的:

//: DDAluminum.java
// Aluminum for double dispatching
package c16.doubledispatch;
import c16.trash.*;

public class DDAluminum extends Aluminum 
    implements TypedBinMember {
  public DDAluminum(double wt) { super(wt); }
  public boolean addToBin(TypedBin[] tb) {
    for(int i = 0; i < tb.length; i++)
      if(tb[i].add(this))
        return true;
    return false;
  }
} ///:~
//: DDPaper.java
// Paper for double dispatching
package c16.doubledispatch;
import c16.trash.*;

public class DDPaper extends Paper 
    implements TypedBinMember {
  public DDPaper(double wt) { super(wt); }
  public boolean addToBin(TypedBin[] tb) {
    for(int i = 0; i < tb.length; i++)
      if(tb[i].add(this))
        return true;
    return false;
  }
} ///:~
//: DDGlass.java
// Glass for double dispatching
package c16.doubledispatch;
import c16.trash.*;

public class DDGlass extends Glass 
    implements TypedBinMember {
  public DDGlass(double wt) { super(wt); }
  public boolean addToBin(TypedBin[] tb) {
    for(int i = 0; i < tb.length; i++)
      if(tb[i].add(this))
        return true;
    return false;
  }
} ///:~
//: DDCardboard.java
// Cardboard for double dispatching
package c16.doubledispatch;
import c16.trash.*;

public class DDCardboard extends Cardboard 
    implements TypedBinMember {
  public DDCardboard(double wt) { super(wt); }
  public boolean addToBin(TypedBin[] tb) {
    for(int i = 0; i < tb.length; i++)
      if(tb[i].add(this))
        return true;
    return false;
  }
} ///:~

每个 addToBin() 内的代码会为数组中的每个 TypeBin 对象调用 add()。但请注意参数:this。对 Trash 的每个子类来说,this 的类型都是不同的,所以不能认为代码“完全”一样——尽管以后在 Java 里加入参数化类型机制后便可认为一样。这是双重派遣的第一个部分,因为一旦进入这个方法内部,便可知道到底是 Aluminum,Paper,还是其他什么垃圾类型。在对 add() 的调用过程中,这种信息是通过 this 的类型传递的。编译器会分析出对 add() 正确的过载版本的调用。但由于 tb[i]会产生指向基础类型 TypeBin 的一个句柄,所以最终会调用一个不同的方法——具体什么方法取决于当前选择的 TypeBin 的类型。那就是第二次派遣。

下面是 TypeBin 的基础类:

//: TypedBin.java
// Vector that knows how to grab the right type
package c16.doubledispatch;
import c16.trash.*;
import java.util.*;

public abstract class TypedBin {
  Vector v = new Vector();
  protected boolean addIt(Trash t) {
    v.addElement(t);
    return true;
  }
  public Enumeration elements() {
    return v.elements();
  }
  public boolean add(DDAluminum a) {
    return false;
  }
  public boolean add(DDPaper a) {
    return false;
  }
  public boolean add(DDGlass a) {
    return false;
  }
  public boolean add(DDCardboard a) {
    return false;
  }
} ///:~

可以看到,过载的 add() 方法全都会返回 false。如果未在衍生类里对方法进行过载,它就会一直返回 false,而且调用者(目前是 addToBin())会认为当前 Trash 对象尚未成功加入一个集合,所以会继续查找正确的集合。

在 TypeBin 的每一个子类中,都只有一个过载的方法会被过载——具体取决于准备创建的是什么垃圾筒类型。举个例子来说,CardboardBin 会过载 add(DDCardboard)。过载的方法会将垃圾对象加入它的集合,并返回 true。而 CardboardBin 中剩余的所有 add() 方法都会继续返回 false,因为它们尚未过载。事实上,假如在这里采用了参数化类型机制,Java 代码的自动创建就要方便得多(使用 C++的“模板”,我们不必费事地为子类编码,或者将 addToBin() 方法置入 Trash 里;Java 在这方面尚有待改进)。

由于对这个例子来说,垃圾的类型已经定制并置入一个不同的目录,所以需要用一个不同的垃圾数据文件令其运转起来。下面是一个示范性的 DDTrash.dat:

c16.DoubleDispatch.DDGlass:54
c16.DoubleDispatch.DDPaper:22
c16.DoubleDispatch.DDPaper:11
c16.DoubleDispatch.DDGlass:17
c16.DoubleDispatch.DDAluminum:89
c16.DoubleDispatch.DDPaper:88
c16.DoubleDispatch.DDAluminum:76
c16.DoubleDispatch.DDCardboard:96
c16.DoubleDispatch.DDAluminum:25
c16.DoubleDispatch.DDAluminum:34
c16.DoubleDispatch.DDGlass:11
c16.DoubleDispatch.DDGlass:68
c16.DoubleDispatch.DDGlass:43
c16.DoubleDispatch.DDAluminum:27
c16.DoubleDispatch.DDCardboard:44
c16.DoubleDispatch.DDAluminum:18
c16.DoubleDispatch.DDPaper:91
c16.DoubleDispatch.DDGlass:63
c16.DoubleDispatch.DDGlass:50
c16.DoubleDispatch.DDGlass:80
c16.DoubleDispatch.DDAluminum:81
c16.DoubleDispatch.DDCardboard:12
c16.DoubleDispatch.DDGlass:12
c16.DoubleDispatch.DDGlass:54
c16.DoubleDispatch.DDAluminum:36
c16.DoubleDispatch.DDAluminum:93
c16.DoubleDispatch.DDGlass:93
c16.DoubleDispatch.DDPaper:80
c16.DoubleDispatch.DDGlass:36
c16.DoubleDispatch.DDGlass:12
c16.DoubleDispatch.DDGlass:60
c16.DoubleDispatch.DDPaper:66
c16.DoubleDispatch.DDAluminum:36
c16.DoubleDispatch.DDCardboard:22

下面列出程序剩余的部分:

//: DoubleDispatch.java
// Using multiple dispatching to handle more
// than one unknown type during a method call.
package c16.doubledispatch;
import c16.trash.*;
import java.util.*;

class AluminumBin extends TypedBin {
  public boolean add(DDAluminum a) {
    return addIt(a);
  }
}

class PaperBin extends TypedBin {
  public boolean add(DDPaper a) {
    return addIt(a);
  }
}

class GlassBin extends TypedBin {
  public boolean add(DDGlass a) {
    return addIt(a);
  }
}

class CardboardBin extends TypedBin {
  public boolean add(DDCardboard a) {
    return addIt(a);
  }
}

class TrashBinSet {
  private TypedBin[] binSet = {
    new AluminumBin(),
    new PaperBin(),
    new GlassBin(),
    new CardboardBin()
  };
  public void sortIntoBins(Vector bin) {
    Enumeration e = bin.elements();
    while(e.hasMoreElements()) {
      TypedBinMember t = 
        (TypedBinMember)e.nextElement();
      if(!t.addToBin(binSet))
        System.err.println("Couldn't add " + t);
    }
  }
  public TypedBin[] binSet() { return binSet; }
}

public class DoubleDispatch {
  public static void main(String[] args) {
    Vector bin = new Vector();
    TrashBinSet bins = new TrashBinSet();
    // ParseTrash still works, without changes:
    ParseTrash.fillBin("DDTrash.dat", bin);
    // Sort from the master bin into the 
    // individually-typed bins:
    bins.sortIntoBins(bin);
    TypedBin[] tb = bins.binSet();
    // Perform sumValue for each bin...
    for(int i = 0; i < tb.length; i++)
      Trash.sumValue(tb[i].v);
    // ... and for the master bin
    Trash.sumValue(bin);
  }
} ///:~

其中,TrashBinSet 封装了各种不同类型的 TypeBin,同时还有 sortIntoBins() 方法。所有双重派遣事件都会在那个方法里发生。可以看到,一旦设置好结构,再归类成各种 TypeBin 的工作就变得十分简单了。除此以外,两个动态方法调用的效率可能也比其他排序方法高一些。

注意这个系统的方便性主要体现在 main() 中,同时还要注意到任何特定的类型信息在 main() 中都是完全独立的。只与 Trash 基础类接口通信的其他所有方法都不会受到 Trash 类中发生的改变的干扰。

添加新类型需要作出的改动是完全孤立的:我们随同 addToBin() 方法继承 Trash 的新类型,然后继承一个新的 TypeBin(这实际只是一个副本,可以简单地编辑),最后将一种新类型加入 TrashBinSet 的集合初化化过程。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文