設計模式解密(22)- 訪問者模式

bzt820801發表於2017-08-19

前言:訪問者模式拆分

 訪問者模式基礎篇 :http://www.cnblogs.com/JsonShare/p/7380772.html

 訪問者模式擴充套件篇 - 分派的概念: http://www.cnblogs.com/JsonShare/p/7381705.html

1、分派的概念

  變數被宣告時的型別叫做變數的靜態型別(Static Type),有些人又把靜態型別叫做明顯型別(Apparent Type);而變數所引用的物件的真實型別又叫做變數的實際型別(Actual Type)。比如:

    Map map = null;

    map = new HashMap();

  宣告瞭一個變數map,它的靜態型別(也叫明顯型別)是Map,而它的實際型別是HashMap。

  根據物件的型別而對方法進行的選擇,就是分派(Dispatch),分派(Dispatch)又分為兩種,即靜態分派和動態分派。

  靜態分派(Static Dispatch)發生在編譯時期,分派根據靜態型別資訊發生。靜態分派對於我們來說並不陌生,方法過載就是靜態分派。

  動態分派(Dynamic Dispatch)發生在執行時期,動態分派動態地置換掉某個方法。

  靜態分派:Java通過方法過載支援靜態分派。

  動態分派:Java通過方法的重寫支援動態分派。

2、動態分派

Java通過方法的重寫支援動態分派。

例項:

package com.designpattern.Visitor.expand.Dynamic;

/**
 * 動態分派
 * @author Json<<json1990@foxmail.com>>
 */
class Dog {
    public void excute(){
        System.out.println("我是dog媽媽");
    }
}

class DogBaby1 extends Dog {
    public void excute(){
        System.out.println("我是dogbaby1");
    }
}

class DogBaby2 extends Dog {
    public void excute(){
        System.out.println("我是dogbaby2");
    }
}

public class Client {
    public static void main(String[] args) {
        Dog baby1 = new DogBaby1();
        baby1.excute();
        
        Dog baby2 = new DogBaby2();
        baby2.excute();
    }
}

變數baby1的靜態型別是Dog,而真實型別是DogBaby1。

excute()方法呼叫的是DogBaby1類的excute()方法,那麼上面列印的就是“我是dogbaby1”;

變數baby2的靜態型別是Dog,而真實型別是DogBaby2。

excute()方法呼叫的是DogBaby2類的excute()方法,那麼上面列印的就是“我是dogbaby2”;

所以,問題的核心就是Java編譯器在編譯時期並不總是知道哪些程式碼會被執行,因為編譯器僅僅知道物件的靜態型別,而不知道物件的真實型別;而方法的呼叫則是根據物件的真實型別,而不是靜態型別。

3、靜態分派

 Java通過方法過載支援靜態分派。

例項:

package com.designpattern.Visitor.expand.Static;

/**
 * 靜態分派
 * @author Json<<json1990@foxmail.com>>
 */
class Dog {
    
}  

class DogBaby1 extends Dog{
    
}  

class DogBaby2 extends Dog{
    
}  
  
class Execute {  
    public void excute(Dog dog){  
        System.out.println("我是dog媽媽");  
    }  
      
    public void excute(DogBaby1 baby1){  
        System.out.println("我是dogbaby1");  
    }  
      
    public void excute(DogBaby2 baby2){  
        System.out.println("我是dogbaby2");  
    }  
}  
  
public class Client {
    public static void main(String[] args) {
        Dog dog = new Dog();  
        Dog baby1 = new DogBaby1();  
        Dog baby2 = new DogBaby2();  
  
        Execute exe = new Execute();  
        exe.excute(dog);  
        exe.excute(baby1);  
        exe.excute(baby2);  
    }
}

顯然,Execute類的excute()方法是由三個方法過載而成的。這三個方法分別接受狗(Dog)、狗baby1(DogBaby1)、狗baby2(DogBaby2)等型別的引數。

那麼在執行時,程式會列印出什麼結果呢?

我是dog媽媽
我是dog媽媽
我是dog媽媽

為什麼呢?三次對excute()方法的呼叫傳入的是不同的引數,分別是dog、baby1、baby2。它們雖然具有不同的真實型別,但是它們的靜態型別都是一樣的,均是Dog型別。

過載方法的分派是根據靜態型別進行的,這個分派過程在編譯時期就完成了。

4、雙(重)分派

   Java是靜態多分派、動態單分派的語言。

   Java不支援動態的雙分派。但是通過使用設計模式,也可以在Java語言裡實現動態的雙重分派。 

  首先,什麼是雙分派?還記得 設計模式解密(22)- 訪問者模式 中舉的例子嗎?

  訪問者模式用到了一種雙分派的技術,所謂雙分派技術就是在選擇一個方法的時候,不僅僅要根據訊息接收者(receiver)的執行時區別(Run time type),還要根據引數的執行時區別。在訪問者模式中,客戶端將具體狀態當做引數傳遞給具體訪問者,這裡完成第一次分派,然後具體訪問者作為引數的“具體狀態”中的方法,同時也將自己this作為引數傳遞進去,這裡就完成了第二次分派。雙分派意味著得到的執行操作決定於請求的種類和接受者的型別。

  雙分派的核心就是這個this物件。

  說到這裡,我們已經明白雙分派是怎麼回事了,但是它有什麼效果呢?就是可以實現方法的動態繫結,我們可以對上面的程式進行修改。

程式碼:

package com.designpattern.Visitor.expand.doubleDispatch;

/**
 * 雙重分派
 * @author Json<<json1990@foxmail.com>>
 */
class Dog {  
    public void accept(Execute exe){  
        exe.excute(this);  
    }  
}  

class DogBaby1 extends Dog{  
    public void accept(Execute exe){  
        exe.excute(this);  
    }  
}  

class DogBaby2 extends Dog{  
    public void accept(Execute exe){  
        exe.excute(this);  
    }  
}  
  
class Execute {  
    public void excute(Dog dog){  
        System.out.println("我是dog媽媽");  
    }  
      
    public void excute(DogBaby1 baby1){  
        System.out.println("我是dogbaby1");  
    }  
      
    public void excute(DogBaby2 baby2){  
        System.out.println("我是dogbaby2");  
    }  
}  
public class Client {
    public static void main(String[] args) {
        Dog dog = new Dog();  
        Dog baby1 = new DogBaby1();  
        Dog baby2 = new DogBaby2();  
  
        Execute exe = new Execute();  
        dog.accept(exe);  
        baby1.accept(exe);  
        baby2.accept(exe);  
    }
}

結果:

我是dog媽媽
我是dogbaby1
我是dogbaby2

從結果可以看出:雙分派實現動態繫結的本質,就是在過載方法委派的前面加上了繼承體系中覆蓋的環節,由於覆蓋是動態的,所以過載就是動態的了!!!

 

PS:原始碼地址   https://github.com/JsonShare/DesignPattern/tree/master 

   

PS:原文地址  http://www.cnblogs.com/JsonShare/p/7381705.html

    

相關文章