• 访问者模式讨论篇:java的动态绑定与双分派


    java的动态绑定

            所谓的动态绑定就是指程执行期间(而不是在编译期间)判断所引用对象的实际类型,根据其实际的类型调用其相应的方法。java继承体系中的覆盖就是动态绑定的,看一下如下的代码:

    1. class Father {  
    2.     public void method(){  
    3.         System.out.println("This is Father's method");  
    4.     }  
    5. }  
    6.   
    7. class Son1 extends Father{  
    8.     public void method(){  
    9.         System.out.println("This is Son1's method");  
    10.     }  
    11. }  
    12.   
    13. class Son2 extends Father{  
    14.     public void method(){  
    15.         System.out.println("This is Son2's method");  
    16.     }  
    17. }  
    18.   
    19. public class Test {  
    20.     public static void main(String[] args){  
    21.         Father s1 = new Son1();  
    22.         s1.method();  
    23.           
    24.         Father s2 = new Son2();  
    25.         s2.method();  
    26.     }  
    27. }  


    运行结果如下:

    This is Son1's method
    This is Son2's method

           通过运行结果可以看到,尽管我们引用的类型是Father类型的,但是运行时却是调用的它实际类型(也就是Son1和Son2)的方法,这就是动态绑定。在java语言中,继承中的覆盖就是是动态绑定的,当我们用父类引用实例化子类时,会根据引用的实际类型调用相应的方法。

     

    java的静态绑定

           相对于动态绑定,静态绑定就是指在编译期就已经确定执行哪一个方法。在java中,方法的重载(方法名相同而参数不同)就是静态绑定的,重载时,执行哪一个方法在编译期就已经确定下来了。看一下代码:

    1. class Father {}  
    2. class Son1 extends Father{}  
    3. class Son2 extends Father{}  
    4.   
    5. class Execute {  
    6.     public void method(Father father){  
    7.         System.out.println("This is Father's method");  
    8.     }  
    9.       
    10.     public void method(Son1 son){  
    11.         System.out.println("This is Son1's method");  
    12.     }  
    13.       
    14.     public void method(Son2 son){  
    15.         System.out.println("This is Son2's method");  
    16.     }  
    17. }  
    18.   
    19. public class Test {  
    20.     public static void main(String[] args){  
    21.         Father father = new Father();  
    22.         Father s1 = new Son1();  
    23.         Father s2 = new Son2();  
    24.   
    25.         Execute exe = new Execute();  
    26.         exe.method(father);  
    27.         exe.method(s1);  
    28.         exe.method(s2);  
    29.     }  
    30. }  


    运行结果如下:

    This is Father's method
    This is Father's method
    This is Father's method

            在这里,程序在编译的时候就已经确定使用method(Father father)方法了,不管我们在运行的时候传入的实际类型是什么,它永远都只会执行method(Father father)这个方法。也就是说,java的重载是静态绑定的。

     

    instanceof操作符与转型

           有时候,我们希望在使用重载的时候,程序能够根据传入参数的实际类型动态地调用相应的方法,也就是说,我们希望java的重载是动态的,而不是静态的。但是由于java的重载不是动态绑定,我们只能通过程序来人为的判断,我们一般会使用instanceof操作符来进行类型的判断。我们要对method(Father father)进行修改,在方法体中判断运行期间的实际类型,修改后的method(Father father)方法如下:

    1. public void method(Father father){  
    2.     if(father instanceof Son1){  
    3.         method((Son1)father);  
    4.     }else if(father instanceof Son2){  
    5.         method((Son2)father);  
    6.     }else if(father instanceof Father){  
    7.         System.out.println("This is Father's method");  
    8.     }  
    9. }  

            请注意,我们必须把判断是否是父类的条件(也就是判断是否为Father类的条件)放到最后,否则将一律会被判断为Father类,达不到我们动态判断的目的。修改代码后,程序就可以动态地根据参数的实际类型来调用相应的方法了。运行结果如下:

    This is Father's method
    This is Son1's method
    This is Son2's method

            但是这种实现方式有一个明显的缺点,它是伪动态的,仍然需要我们来通过程序来判断类型。假如Father有100个子类的话,还是这样来实现显然是不合适的。必须通过其他更好的方式实现才行,我们可以使用双分派方式来实现动态绑定。

          

    用双分派实现动态绑定

            首先,什么是双分派?还记得23种设计模式(9):访问者模式中一开始举的例子吗?

            类A中的方法method1和method2的区别就是,method2是双分派。我们可以看一下java双分派的特点:首先要有一个访问类B,类B提供一个showA(A a) 方法,在方法中,调用类A的method1方法,然后类A的method2方法中调用类B的showA方法并将自己作为参数传给showA。双分派的核心就是这个this对象。说到这里,我们已经明白双分派是怎么回事了,但是它有什么效果呢?就是可以实现方法的动态绑定,我们可以对上面的程序进行修改,代码如下:

    1. class Father {  
    2.     public void accept(Execute exe){  
    3.         exe.method(this);  
    4.     }  
    5. }  
    6. class Son1 extends Father{  
    7.     public void accept(Execute exe){  
    8.         exe.method(this);  
    9.     }  
    10. }  
    11. class Son2 extends Father{  
    12.     public void accept(Execute exe){  
    13.         exe.method(this);  
    14.     }  
    15. }  
    16.   
    17. class Execute {  
    18.     public void method(Father father){  
    19.         System.out.println("This is Father's method");  
    20.     }  
    21.       
    22.     public void method(Son1 son){  
    23.         System.out.println("This is Son1's method");  
    24.     }  
    25.       
    26.     public void method(Son2 son){  
    27.         System.out.println("This is Son2's method");  
    28.     }  
    29. }  
    30.   
    31. public class Test {  
    32.     public static void main(String[] args){  
    33.         Father father = new Father();  
    34.         Father s1 = new Son1();  
    35.         Father s2 = new Son2();  
    36.   
    37.         Execute exe = new Execute();  
    38.         father.accept(exe);  
    39.         s1.accept(exe);  
    40.         s2.accept(exe);  
    41.     }  
    42. }  

            可以看到我们修改的地方,在Father,Son1,Son2中分别加入一个双分派的方法。调用的时候,原本是调用Execute的method方法,现在改为调用Father的accept方法。运行结果如下:

    This is Father's method
    This is Son1's method
    This is Son2's method

            运行结果符合我们的预期,实现了动态绑定。双分派实现动态绑定的本质,就是在重载方法委派的前面加上了继承体系中覆盖的环节,由于覆盖是动态的,所以重载就是动态的了,与使用instanceof操作符的效果是一样的(用instanceof操作符可以实现重载方法动态绑定的原因也是因为instanceof操作符是动态的)。但是与使用instanceof操作符实现动态绑定相比,双分派方式的可扩展性要好的多。

  • 相关阅读:
    Delphi泛型系列(很不错)[转静候良机]
    数组的排序
    数据存储到流几种形式(数据流 TStream)
    [转]Delphi TStream详解
    Delphi匿名方法[转 静候良机]
    神一样的崇拜这个女人...打破了我对我们苦b程序员极限的了解
    sql server cte语法
    GdiPlus[49]: 图像(一) 概览
    GdiPlus[51]: 图像(三) 关于呈现
    GdiPlus[47]: IGPMatrix 矩阵(二)
  • 原文地址:https://www.cnblogs.com/xhk1228/p/3194846.html
Copyright © 2020-2023  润新知