• 设计模式:话说分派


    《话说分派》  


    一、引言 
        这篇文章,完全是为了更好的讲解访问者(Visitor)模式而写的。让我们进入这扑朔迷离 
    的分派世界吧(是不是有点夸张了,汗)。 


    二、名词解释 
        先来解释下分派的意思吧。。 
        在OO (object-oriented)语言中使用了继承来描述不同的类之间的“社会关系”——类型 
    层次。而这些类实例化的对象们则是对这个类型层次的体现。因此大部分OO 语言的对象都 
    存在两个身份证:静态类型和实际类型。所谓静态类型,就是对象被声明时的类型;而实际 
    类型则便是创建对象时的类型。举个例子: 
        B 是A 的子类。则 


        A object1 = new B ( ); 
    中object1 的静态类型便是A,而实际类型却是B。在Java 语言中,编译器会根据对象的静 
    态类型来检查错误;而在运行时,则使用对象的真实身份。 
         OO 还有一个重要的特点:一个类中可以存在两个相同名称的方法,而它们是根据参数 
    类型的不同来区分的。 
        正因以上两个原因,便产生了分派——根据类型的不同来选择不同的方法的过程——OO 
    语言的重要特性。 


    三、分类 
        分派可以发生在编译期或者是运行期。因此按此标准,分派分为静态分派和动态分派。 
        在程序的编译期,只有对象的静态类型是有效的,因此静态分派就是根据对象(包括参 
    数对象)的静态类型来选择方法的。最典型的便是方法重载(overloading)。 
        在运行期,动态分派会根据对象的实际类型来选择方法。典型的例子便是方法重置 
     (overriding) 
        而OO 语言正是由以上两种分派方式来提供多态特性的。 
        按照选择方法时所参照的类型的个数,分派分为单分派和多分派。OO 语言也因此分为 
    了单分派(Uni-dispatch)语言和多分派(Multi-dispatch)语言。比如Smalltalk  就是单分 
    派语言,而CLOS 和Cecil 就是多分派语言。 
        说道多分派,就不得提到另一个概念:多重分派(multiple dispatch)。它指由多个单分 
    派组成的分派过程(而多分派则往往不能分割的)。因此单分派语言可以通过多重分派的方 
    式来实现和多分派语言一样的效果。 
        那么我们熟悉的Java 语言属于哪一种分派呢? 


    四、Java 分派实践 
        先来看看在Java 中最常见的特性:重载(overloading)与重置(overriding)。 
        下面是重载的一个具体的小例子,这是一个再简单不过的代码了: 


    //Test For OverLoading 
    public class Test{ 
         public void doSomething(int i){ 
            System.out.println("doString int = "+ i ); 
         } 

           public void doSomething(String s){ 
                System.out.println("doString String = "+ s); 
           } 
           public void doSomething(int i , String s){ 
                System.out.println("doString int = "+ i +" String = "+ s); 
           } 
           public static void main(String[] rags){ 
                Test t = new Test(); 
                int i = 0; 
                t.doSomething(i); 
           } 

          没什么好稀奇的,你对这部分知识已经熟练掌握了,那么你对下面这段代码的用意也一 
    定了如指掌了吧。 


    //Test For Overriding 
    public class Test{ 
           public static void main(String[] rags){ 
                Father f = new Father(); 
                Father s = new Son(); 
                f.dost(); 
                s.dost(); 
           } 

    class Father { 
           public void dost(){ 
                System.out.println("Welcome Father!"); 
           } 

    class Son extends Father{ 
           public void dost(){ 
                System.out.println("Welcome Son!"); 
           } 

          那么下面这个代码呢? 


    public class Test{ 
           public static void main(String[] rags){ 
                Father f = new Father(); 
                Father s = new Son(); 


                f.dost(1); 
                s.dost(4); 
                s.dost("dispatchTest"); 
                //s.dost("test" , 5); 
           } 


    class Father { 


           public void dost(int i){ 
                System.out.println("Welcome Father! int = "+ i); 
           } 
           public void dost(String s){ 
                System.out.println("Welcome Father! String = "+ s); 
           } 

    class Son extends Father{ 


           public void dost(int i){ 
                System.out.println("Welcome Son! int = "+i); 
           } 
           public void dost(String s ,int i ){ 
                System.out.println("Welcome Son! String = "+s+" int = "+i); 
           } 

          在编译期间,编译器根据f、s 的静态类型来为他们选择了方法,当然都选择了父类Father 
    的方法。而到了运行期,则又根据s 的实际类型动态的替换了原来选择的父类中的方法。这 
    便是结果产生的原因。 
          如果把上面代码中的注释去掉,则会出现编译错误。原因便是在编译期,编译器根据s 
    的静态类型Father 找不到带有两个参数的方法dost。 
          再来一个,可要注意看了: 


    public class Test{ 
           //这几个方法,唯独的不同便在这参数上 


           public void dost(Father f , Father f1){ 
                System.out.println("ff"); 
           } 
           public void dost(Father f , Son s){ 
                System.out.println("fs"); 
           } 
           public void dost(Son s , Son s2){ 
                System.out.println("ss"); 
           } 
           public void dost(Son s , Father f){ 
                System.out.println("sf"); 
           } 


           public static void main(String[] rags){ 
                Father f = new Father(); 
                Father s = new Son(); 
                Test t = new Test(); 
              t.dost(f , new Father()); 
              t.dost(f , s); 
              t.dost(s, f); 
          } 

    class Father {} 
    class Son extends Father{} 
         执行结果没有像预期的那样输出ff、fs、sf 而是输出了三个ff。为什么?原因便是在编 
    译期,编译器使用s 的静态类型为其选择方法,于是这三个调用都选择了第一个方法;而在 
    运行期,由于Java 仅仅根据方法所属对象的实际类型来分派方法,因此这个“错误”就没有 
    被纠正而一直错了下去…… 


         可以看出,Java 在静态分派时,可以根据n (n>0)个参数类型来选择不同的方法,这 
    按照上面的定义应该属于多分派的范围。而在运行期时,则只能根据方法所属对象的实际类 
    型来进行方法的选择,这又属于单分派的范围。 
         因此可以说Java 语言支持静态多分派和动态单分派。 


    五、小插曲 
         你看看下面的代码会怎么执行呢? 


    public class Test{ 
          public static void main(String[] rags){ 
              Father f = new Father(); 
              Father s = new Son(); 


              System.out.println("f.i " + f.i); 
              System.out.println("s.i " + s.i); 
              f.dost(); 
              s.dost(); 
          } 

    class Father { 
          int i = 0 ; 
          public void dost(){ 
              System.out.println("Welcome Father!"); 
          } 

    class Son extends Father{ 
          int i = 9 ; 
          public void dost(){ 
              System.out.println("Welcome Son!"); 
          } 

    运行结果: 


       \>java Test 
       f.i 0 
       s.i 0 
       Welcome Father! 
       Welcome Son! 
       产生的原因是Java 编译和运行程序的机制。“数据是什么”是由编译时决定的;而“方法是 
    哪个”则在运行时决定。 


    六、双重分派 
          Java  不能支持动态多分派,但是可以通过代码设计来实现动态的多重分派。这里举一 
    个双重分派的实现例子。 
         大致的思想便是通过一个参数来传递JVM 不能判断的类型。通过Java 的动态单分派来 
    完成一次分派后,在方法中使用instanceof 来判断参数的类型,进而决定执行哪个相关方法。 


          public class Test{ 
          public static void main(String[] rags){ 
              Father f = new Father(); 
              Father s = new Son(); 
              s.dosh(f); 
              s.dosh(s); 
              f.dosh(s); 
              f.dosh(f); 
          } 

    class Father { 
          public void dosh(Father f){ 
              if(f instanceof Son){ 
                    System.out.println("Here is Father's Son"); 
              }else if(f instanceof Father){ 
                    System.out.println("Here is Father's Father"); 
              } 
          } 



    class Son extends Father{ 
          public void dosh(Father f){ 
              if(f instanceof Son){ 
                    System.out.println("Here is Son's Son"); 
              }else if(f instanceof Father){ 
                    System.out.println("Here is Son's Father"); 
              } 
          } 

    执行结果: 


       Here is Son's Father 
       Here is Son's Son 
      Here is Father's Son 
      Here is Father's Father 
        呵呵,慢慢在代码中琢磨吧。用这种方式来实现双重分派,思路比较简单清晰。但是对 
    于复杂一点的程序,则代码显得冗长,不易读懂。而且添加新的类型比较麻烦,不是一种好 
    的设计方案。访问者(Visitor)模式则较好的解决了这种模式的不足。至于访问者模式的实 
    现…… 

        请关注我的《深入浅出访问者模式》。 

    下载:

    http://download.csdn.net/detail/undoner/5335717

    深入浅出设计模式-中文版


  • 相关阅读:
    [BJOI2019] 光线
    C# 从零开始写 SharpDx 应用 笔刷
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    BAT 脚本判断当前系统是 x86 还是 x64 系统
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    win2d 通过 CanvasActiveLayer 画出透明度和裁剪
    PowerShell 拿到显卡信息
    PowerShell 拿到显卡信息
    win10 uwp 如何使用DataTemplate
    win10 uwp 如何使用DataTemplate
  • 原文地址:https://www.cnblogs.com/wuyida/p/6301005.html
Copyright © 2020-2023  润新知