• 设计模式解密(22)- 访问者模式


    前言:访问者模式拆分

     访问者模式基础篇 :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

        

  • 相关阅读:
    delphi RTTI 反射技术
    delphi 自我删除和线程池(1000行代码,需要仔细研究)
    寻找两个已序数组中的第k大元素
    OpenCV中的神器Image Watch
    PYTHON 之 【RE模块的正则表达式学习】
    Call U
    微软IE11浏览器的7大变化
    集群应用及运维经验小结
    逆序对:从插入排序到归并排序
    Jquery 图片轮播实现原理总结
  • 原文地址:https://www.cnblogs.com/JsonShare/p/7381705.html
Copyright © 2020-2023  润新知