大B:“靜態分派,動態分派,多分派,單分派是visitor模式準備。”
小A:“visitor模式準備?能不能詳細講講,我不明白。”
大B:“可以。”
1、靜態分派:
(1)定義:發生在編譯時期,分派根據靜態類型信息發生,重載就是靜態分派。
(2)什麼是靜態類型:變量被聲明時的類型是靜態類型。
什麼是動態類型:變量所引用的對象的真實類型。
(3)有兩個類,BlackCat,WhiteCat都繼承自。
如下調用
classCat{}
classWhiteCatextendsCat{}
classBlackCatextendsCat{}
publicclassPerson{
publicvoidfeed(Catcat){
System.out.println(“feedcat”);
}
publicvoidfeed(WhiteCatcat){
System.out.println(“feedWhiteCat”);
}
publicvoidfeed(BlackCatcat){
System.out.println(“feedBlackCat”);
}
publicstaticvoidmain(String[]args){
Catwc=newWhiteCat();
Catbc=newBlackCat();
Personp=newPerson();
p.feed(wc);
p.feed(bc);
}
}
運行結果是:
feedcat
feedcat
這樣的結果是因爲重載是靜態分派,在編譯器執行的,取決於變量的聲明類型,因爲wc,bc都是Cat所以調用的都是feed(Catcat)的函數。
2、動態分派
定義:發生在運行期,動態分派,動態的置換掉某個方法。
還是上面類似的例子:
classCat{
publicvoideat(){
System.out.println(“cateat”);
}
}
publicclassBlackCatextendsCat{
publicvoideat(){
System.out.println(“blackcateat”);
}
publicstaticvoidmain(String[]args){
Catcat=newBlackCat();
cat.eat();
}
}這個時候的結果是:
blackcateat
這樣的結果是因爲在執行期發生了向下轉型,就是動態分派了。
3、單分派:
定義:根據一個宗量的類型進行方法的選擇。
4、多分派:
(1)定義:根據多於一個宗量的類型對方法的選擇。
(2)說明:多分派其實是一系列的單分派組成的,區別的地方就是這些但分派不能分割。
(3)Java是動態單分派,靜態多分派語言。
大B:“訪問同一類型的集合類是我們最常見的事情了,我們工作中這樣的代碼太常見了。”
Iteratorie=list.iterator();
while(ie.hasNext()){
Person
p=(Person)ie.next();
p.doWork();
}
這種訪問的特點是集合類中的對象是同一類對象Person,他們擁有功能的方法run,我們調用的恰好是這個共同的方法。在大部份的情況下,這個是可以的,但在一些複雜的情況,如被訪問者的繼承結構複雜,被訪問者的並不是同一類對象,也就是說不是繼承自同一個根類。方法名也並不相同。例如JavaGUI中的事件就是一個例子。
例如這樣的問題,有如下類和方法:
類:PA,方法:runPA();
類:PB,方法:runPB();
類:PC,方法:runPC();
類:PD,方法:runPD();
類:PE,方法:runPE();
有一個集合類。
Listlist=newArrayList();
list.add(newPA());
list.add(newPB());
list.add(newPC());
list.add(newPD());
list.add(newPE());
……
大B:“要求能訪問到每個類的對應的方法。我們第一反應應該是這樣的。”
Iteratorie=list.iterator();
while(ie.hasNext()){
Objectobj=ie.next();
if(objinstanceofPA){
((PA)obj).runPA();
}elseif(objinstanceofPB){
((PB)obj).runPB();
}elseif(objinstanceofPC){
((PC)obj).runPC();
}elseif(objinstanceofPD){
((PD)obj).runPD();
}elseif(objinstanceofPE){
((PE)obj).runPE();
}
}
大B:“當數目變多的時候,維護ifelse是個費力氣的事情:仔細分析if,else做的工作,首先判斷類型,然後根據類型執行相應的函數。”
小A:“如何才能解決這兩個問題呢?”
大B:“首先想到的是Java的多態,多態就是根據參數執行相應的內容,能很容易的解決第二個問題,我們可以寫這樣一個類。”
publicclassvisitor{
publicvoidrun(PApa){
pa.runPA();
}
publicvoidrun(PBpb){
pb.runPB();
}
publicvoidrun(PCpc){
pc.runPC();
}
publicvoidrun(PDpd){
pd.runPD();
}
publicvoidrun(PEpe){
pe.runPE();
}
}
大B:“這樣只要調用run方法,傳入對應的參數就能執行了。”
小A:“還有一個問題就是判斷類型。”
大B:“由於重載(overloading)是靜態多分配。Java語言本身是支持‘靜態多分配’的。所以造成重載只根據傳入對象的定義類型,而不是實際的類型,所以必須在傳入前就確定類型,這可是個難的問題,因爲在容器中對象全是Object,出來後要是判斷是什麼類型必須用if(xxinstanceofxxx)這種方法。”
小A:“如果用這種方法豈不是又回到了原點,有沒有什麼更好的辦法呢?我們知道Java還有另外一個特點,覆寫(overriding),而覆寫是‘動態單分配’的,那如何利用這個來實現呢?”
大B:“看下邊這個方法:我們讓上邊的一些類PA、PB、PC、PD、PE都實現一個接口P,加入一個方法,accept()。”
publicvoidaccept(visitorv){
//把自己傳入。
v.run(this);
}
然後在visitor中加入一個方法
publicvoidrun(Pp){
//把自己傳入。
p.accept(this);
}
//這樣你在遍歷中可以這樣寫
Visitorv=newVisitor();
Iteratorie=list.iterator();
while(ie.hasNext()){
Pp=(P)ie.next();
p.accept(v);
}
}
大B:“首先執行的是‘把自己傳入2’,在這裡由於Java的特性,實際執行的是子類的accept(),也就是實際類的accept然後是‘把自己傳入1’,在這裡再次把this傳入,就明確類型,OK我們巧妙的利用overriding解決了這個問題。其實歸納一下第二部分,一個關鍵點是‘自己認識自己’,是不是很可笑。”