代码改变世界

让我最后罗嗦一下Visitor模式——关于双重分派

2005-08-20 13:21 FantasySoft 阅读(...) 评论(...) 编辑 收藏

        昨天罗嗦了一下Visitor模式idior以及Cavingdeep兄都写下了很多促使我进一步去思考的Feedback,在这里谢谢两位了!感谢的话说完了,让我们言归正传——开始罗嗦,呵呵。
        在昨天的Post中我提到了Visitor模式中最重要的一点是:double-dispatch(双重分派)。说来惭愧,昨天在写post的时候,我仅仅在震宇兄的Post中对double-dispatch有了概念上的理解,并且认为accept方法是为了实现double-dispatch的。然而idior和Cavingdeep兄的问题让我心里没了谱:accept为什么要存在,为什么不直接visit呢?早上在看了Cavingdeep兄的Post之后,我想要解决这个问题,得从到底什么是双重分派入手了。于是google了一番,终于有点开窍了。
        在说双重分派以前,我们先来看看以下代码[1]

SuperClass a = new SubA();
SuperClass parameter 
= new
 SubB();
a.commonMethod(parameter);

我们都知道由于动态类型绑定,尽管a声明为SuperClass类型,但a.commonMethod调用的是SubA中override的commonMethod,这就是多态的一种体现。如果在SubA类中有这样的重载方法:

commonMethod(SuperClass arg)  }
commonMethod(SubA arg) 
 }
commonMethod(SubB arg) 
 }

那么a.commonMethod调用的又是哪个方法呢?你的心里有非常肯定的答案吗?写段代码尝试一下,你会发现第一个commonMethod被调用了。这里就引出了一个问题:动态类型绑定只会体现在方法的调用者身上,而方法的参数类型则会在编译期由编译器决定
        如果参数类型也能够在运行期决定,那么哪个commonMethod被调用就由方法调用者和方法参数共同在运行期决定了。那么如何实现参数类型在运行期绑定呢?既然方法调用者的类型是运行期才确定的,那么我们就可以反客为主了,将方法参数变成方法调用者。

commonMethod(SuperClass arg) {
    arg.commonMethod(
this
);
}

至此,您应该明白双重分派的涵义了吧?哪个commonMethod最终被调用经过两次运行期类型绑定才确定下来,这样的过程就是双重分派了。由此延伸开来,多重分派也就不难理解了。
        在双重分派当中,还有一个关键细节,您是否发现了呢?那就是您所期待的SubA中的CommonMethod(SubB arg)方法被调用的情况并没有出现,反而是SubB中的CommonMethod(SubA arg)的方法被调用了。不管怎么样,双重分派给程序带来了更多的灵活性。        
        说完了双重分派,我们再回到Visitor模式上来。Visitor模式其实就是将以上所说的commonMethod从数据对象中抽离出来封装至单独的类中,那么为了维持原有的双重分派,accept方法就派上了用场。没有了accept,就无法在数据操作从数据对象中分离出来的同时保持双重分派。同时,您也会发现commonMethod有一个非常大的特点:它是一个跨对象操作的方法。对应到Visitor模式里众多的Visitor类,重载的visit方法正好体现了跨对象操作的特点。
       
        也许我讲述的还不是很清楚,有兴趣的朋友可以参考以下这篇给我带来很大启发的文章。
         
        [1] Double Dispatch Mechanism