程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 實現雙重派遣

實現雙重派遣

編輯:關於JAVA

記住多形性只能通過方法調用才能表現出來,所以假如想使雙重派遣正確進行,必須執行兩個方法調用:

在每種結構中都用一個來判斷其中的類型。在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的集合初化化過程。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved