[最后编辑2014.9.29]

本节应用命令模式,在Java中模拟双分派。理解本节后,访问者模式(visitor pattern)手到擒来。

1. 分派

分派/ dispatch是指按照对象的实际类型为其绑定对应方法体的过程。

例如有X类及其两个子类X1、X2,它们都实现了实例方法m()——通常子类X1、X2的方法前应该加@Override,所以有3个m()。

1)现在声明X类型变量a,请问消息a.m()绑定3个中的哪一个方法呢?当前主流的面向对象语言如C++、Java、C#等,都会按照a的实际类型,为其绑定对应方法体。

【例子3-22似乎多余,删除!】

例程3-22的Test中,声明的X类型变量a、b按照配置文件分别持有X1和X2对象的引用,所以a.m()绑定它指向对象的实际类型X1的方法体,b.m()绑定X2.m()。

对于消息表达式a.m(b,c),按照一个对象的实际类型绑定对应方法体,称为单分派。当然,这个“一个对象”比较特殊,每一个消息表达式a.m(b,c)只有一个消息接收者,这个“一个对象”就是指消息接收者。所以,仅按照消息接收者的实际类型绑定实际类型提供的方法体,即单分派(singledispatch),就是面向对象中的动态绑定!

2)假设对于消息表达式a.m(b,c),如果能够按照a、b和c的实际类型为其绑定对应方法体,则称为三分派。简单起见,研究双分派(double dispatch)就够了。Java、C#等语言不支持双分派。原因你应该清楚:对于foo(X)、foo(X1)和foo(X2)这些重载的方法,Java在编译时,就为foo(b)按照b的声明类型静态绑定了foo(X)这个的方法体。

所谓的双分派,则是希望a.foo(b)能够①按照a的实际类型绑定其override的方法体,而且能够②按照b的实际类型绑定其重载的方法即foo(Y)、foo(Y1)、foo(Y2)中的适当方法体。【相关概念,可以参考《设计模式.5.11访问者模式》p223】

遗憾的是,Java不支持双分派。Java在编译时,就为foo(b)按照b的声明类型X静态绑定了foo(X)这个的方法体。(Java重载方法的匹配算法,请参考【编程导论·2.3.1】)

2. 区分重载的方法 由于动态绑定的m()方法在本讨论中属于多余,因而以Y、Y1和Y2替代X层次,而且Y、Y1和Y2都是空类体。所有需要的重载的方法foo(Y)、foo(Y1)和foo(Y2)等等,全部放在OverloadFoo类中。

例程 3 24 重载的foo

package method.command.doubleDispatch;

import static tool.Print.*;

public class OverloadFoo{

public void foo(Y y) { pln("foo(Y)"); }

public void foo(Y1 y){ pln("foo(Y1)");}

public void foo(Y2 y){ pln("foo(Y2)");}

/**

* (Run-Time Type Identification、RTTI

*/

public void foo_RTTI(Y y){

if(y instanceof Y1){

Y1 temp = (Y1)y;

foo(temp);

}else if(y instanceof Y2){

Y2 temp = (Y2)y;

foo(temp);

}else{

foo(y);

}

}

}测试代码

public static void Y单分派(){

Y y = (Y)God.create("3-18-Y");

new OverloadFoo().foo(y);

new OverloadFoo().foo_RTTI(y);

}

配置文件创建Y1对象时,输出为:

foo(Y) //Java的静态绑定

foo(Y1) //RTTI

Java中可以使用运行时类型识别(Run-Time TypeIdentification、RTTI)技术,即使用关键字instanceof判断实际类型。虽然声明类型为父类Y,程序中按照实际类型重新声明temp,并将参数向下造型。RTTI虽然代码简洁,但使用分支语句不够优雅。另外,①程序员还要注意,具体类型判断在前;②RTTI将占用较多的运行时间和空间。

[编程导论·2.3.1] 中说明:“重载一个方法,真正做的事情是定义了若干不同的方法,不过‘碰巧’使用了相同的方法名”。从调用foo(Y)的模块如客户端Test的角度看,重载的foo(Y)、foo(Y1)、foo(Y2)与不同名的fooY()、fooY1()、fooY2()没有区别。于是,有两条路线:

Test希望进行统一的调用——无视被调的方法名,我们可以采用命令模式。

当然,将重载的方法改名为fooY()、fooY1()、fooY2(),给外界感觉是双分派——这也是访问者模式(visitor pattern)采用的方式。

package method.command.doubleDispatch;

public abstract class Command{

OverloadFoo handler = new OverloadFoo();

public abstract void foo(Y y);//变化:执行者已知OverloadFoo

}

package method.command.doubleDispatch;

public class FooY1 extends Command {

@Override  public void foo(Y y)  {

handler.foo((Y1)y);

}

}//FooY和 FooY2略这个Command和

3.4 命令模式(5.2)中简单的Command接口有些小小的变化:命令的执行者已知为OverloadFoo(因为它包括了3个重载的foo方法);抽象方法foo带有参数。

Command的子类FooY1,指明执行者调用重载的foo(Y1)方法。

public static void 模拟双分派(){

Y y = (Y)God.create("3-18-Y");//Y1对象

Command cmd = new FooY();

cmd.foo(y);

cmd = new FooY1();

cmd.foo(y);

cmd = new FooY2();//任务不可执行

//cmd.foo(y);

}现在,创建一个Y对象(实际类型Y1)后,按照不同的命令,测试结果:

Y1.m()

foo(Y)

Y1.m()

foo(Y1)

命令模式,使得用户类Test无视被调的方法名,下达统一的命令foo(y);而执行者按照命令对象的不同,执行不同的方法体——这里就将重载的方法区分开来了。

图1 应用命令模式

3.合并类层次

上图中有两个类层次Y和Command,图形显得比较复杂。我们发现Command的普适命令foo(Y y)在其子类FooY1中的代码为:

@Override  public void foo(Y y)  {

handler.foo((Y1)y);

}

我们如何利用Java的多态性避免这种指定性的强制类型转换呢?要点就是命令的执行者不再是固定的OverloadFoo ,而是FooY1自己——命令执行者将是Y1和Y2!

现在,开启Z系列。

命令接口Foo定义handleFoo()方法。

package method.command.doubleDispatch;

public interface Foo{

public void handleFoo();

}与X和Y对应的Z,与X不同之处为

foo(Foo  )!

Z的类层次成为Command/Foo的子类型。Z implements Foo使得Z的子类自动成为Foo的子类型,(其实Z本身不需要成为 Foo的子类型,你可以将Z的所有子类Z1、Z2 implements Foo)

package method.command.doubleDispatch;

import static tool.Print.*;

public abstract class Z implements Foo{

public void m(){

pln(" Z.m()");

}

public void foo(Foo z ){//示例代码,可以为空方法体

p(" Z.foo(Foo)-");

this.m();

}

//@Override public void handleFoo(){} //可有可无

}现在,Z1的代码如下:

package method.command.doubleDispatch;

import static tool.Print.*;

public class Z1 extends Z {

@Override public void m(){

pln(" Z1.m()");

}

/*事实上,意味着重载foo(Z1)*/

@Override public void foo(Foo z ){

p("Z1.");

z.handleFoo();//执行者z动态绑定

this.m();

}

private void foo(){

p("foo(Z1)-");

}

@Override public void handleFoo(){

this.foo();

}

}

package method.command.doubleDispatch;

import tool.God;

public class Test{

public static void Z双分派(){

Z z1 = (Z)God.create("3-18-Z1");//Z1对象

Z z2 = (Z)God.create("3-18-Z2");//Z1对象

z1.foo(z1);

z1.foo(z2);

z2.foo(z1);

z2.foo(z2);

}

}测试结果:

Z1.foo(Z1)- Z1.m()

Z1.foo(Z2)- Z1.m()

Z2.foo(Z1)- Z2.m()

Z2.foo(Z2)- Z2.m()

对于消息a.foo(b),假设a、b声明为Z类型变量,目前我们模拟了双分派Double Dispatch。

图2 简洁的双分派结构

但是,这种模拟存在一个问题:Z1类中的foo(Foo z )替代了原始的重载方法foo(Z1),那么Z1类中并没有提供原始的重载方法foo(Z2)的代码。

对于客户Test而言,它声明X的对象x和Z的对象z,调用x .foo(z),应该能够找到4个方法体。你可以修改上述代码,使得消息a.foo(b),其中a声明为X类型变量、b声明为Z类型变量。

可以将重载的方法改名,使外界感觉是双分派。注意:程序中避免使用RTTI技术。

按照模拟的双分派模型,会成为2*2的表示方式,这是两个步骤的叠加而形成4个处理流程。没有双分派机制,那么区分重载操作是不可行的,对于X1和X2的对象,需要方法fooZ1(Z1)和fooZ2(Z2),与a.foo(b)对应的,有4个方法体。

网上看见一个段子:

【双重分派:

对了,你在上面的例子中体会到双重分派的实现了没有?

首先在客户程序中将具体访问者模式作为参数传递给具体元素角色(加亮的地方所示)。这便完成了一次分派。

进入具体元素角色后,具体元素角色调用作为参数的具体访问者模式中的visitor方法,同时将自己(this)作为参数传递进去。具体访问者模式再根据参数的不同来选择方法来执行(加亮的地方所示)。这便完成了第二次分派。】

你咋不说打乒乓球是n次分派?

事实上,打乒乓球是我们获得命令模式的基础,【从0开始,研究一下Controller如何才能够忘记/无视被调的方法名?】

java并行任务dispatch_Java模拟 双分派Double Dispatch相关推荐

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

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

  2. cacheinterceptor第二次访问没被调用_双分派访问者模式的前世今生

    ❝ 学了那么久的Java,你是否知道Java是属于单分派语言还是双分派语言?什么?单分派和双分派是什么意思还不知道?了解了分派机制,就能明白访问者模式的前世今生了. ❞ 访问者模式是23种设计模式当中 ...

  3. java 双分派_双分派 和 访问者模式详解 | 学步园

    为什么 网上的人都说 java 只支持 单分派不支持双分派? 这段代码摘子某书[code=Java] public class Dispatch{ static class QQ{} static c ...

  4. Java的单分派与双分派以及访问者模式的关系

    引言 在学习访问者模式的过程中了解到了单双分派这一词,本文将重点为大家解释Java中的单分派与双分派到底是什么,以及为什么会与访问者模式扯上关系 首先,我们给出一个定义:"Java是一种支持 ...

  5. 访问者模式用到了一种双分派的技术——静态分派和动态分派

    扩展 访问者模式用到了一种双分派的技术. 1,分派: 变量被声明时的类型叫做变量的静态类型,有些人又把静态类型叫做明显类型: 而变量所引用的对象的真实类型又叫做变量的实际类型. 比如 Map map ...

  6. (二十二)访问者模式详解(伪动态双分派) - 转

    作者:zuoxiaolong8810(左潇龙),转载请注明出处. 本次LZ和各位分享一下访问者模式,从场景.设计初衷以及实现方面来说,访问者模式算是LZ即将写到的24种设计模式当中,最复杂也是最难理解 ...

  7. 浅谈:数据结构之双链表结构与代码模拟双链表的实现

    双链表 本文是观看尚硅谷韩老师数据结构与算法根据老师讲解自己做的笔记,部分信息收集网络 与单链表区别 逻辑上没有区别.他们均是完成线性表的内容.主要的区别是结构上的构造有所区别. 对于单链表: 对于一 ...

  8. java math.cos_Java Math类静态double cos(double d)示例

    java math.cos 数学类静态双cos(double d) (Math Class static double cos(double d)) This method is available ...

  9. Java制作一个盒子程序_编写一个简单的Java程序,模拟计算器的功能。

    提问:编写一个简单的Java程序,模拟计算器的功能. 网友回答: 程序参考: import java.awt.*; import java.awt.event.ActionEvent; import ...

最新文章

  1. oracle改表结构,Oracle修改表结构
  2. BGP、MPLS是怎么组合到一起的?
  3. Linux shell的和||--转载
  4. LeetCode OJ - Valid Palindrome
  5. 4245: KI的斐波那契 递归
  6. 【BZOJ4247】挂饰,又一个奇特的背包
  7. Smack+Openfire 接收和发送文件
  8. Grow heap (frag case) to 6.437MB for 1114126-byte allocation
  9. AI-多云互联,网络通信的“自动驾驶
  10. 安装驱动省心办法:驱动总裁
  11. GROMACS .mdp 选项翻译及笔记
  12. 浅谈大数据时代web数据可视化探析
  13. C# Windows系统音量调节Demo源码
  14. 图形学初步----------多边形填充算法
  15. 低代码平台要怎么选?便宜其实也有好货!
  16. 腾讯云Cannot parse privatekey: unsupported key format问题解决
  17. return返回值详解
  18. oracle配置odbc数据源
  19. TypeError: __new__() missing 1 required positional argument: ‘exemplar‘
  20. ASP.NET+C#+Sql Server数据库0968 校园二手物品交易网站的设计与实现-毕业设计

热门文章

  1. 深度学习崛起那年,百度差点签下Hinton
  2. YOLOv5是真的吗?并不比YOLOv4强,不配这个名字
  3. 彻底搞懂机器学习中的正则化
  4. 这届清华新生太难了吧!C++作业难到上热搜,特奖都说做不了,大厂猎头已密切关注...
  5. 世界上有哪些代码量很少,但很牛逼很经典的算法或项目案例?
  6. 成天说要删库跑路,这次真的有人干了
  7. 神操作!美国程序员把工作外包给中国程序员,上班摸鱼吸猫年入 20 万美元
  8. 论文里常出现的可扩展性(Scalability)是什么意思呢?
  9. 我的第一个VUE示例
  10. 由于获得较好的处理机资源,因此将之前的数据整合到一起。