什么是双重分派

什么是分派(dispatch)

首先我们需要理解「分派」的含义。分派就是将方法调用与对应的具体方法绑定起来。而判断的依据有两点,这两者可称为「宗量」:

  1. 方法的接收者,也就是哪个对象调用了这个方法
  2. 方法的参数,调用方法时传递过来的参数

而不同的判断方法也就产生了不同的分派类型:

静态分派 V.S. 动态分派

单重分派 V.S. 多重分派

下面以我的理解简单地总结一下这几个名词的含义

静态分派

静态分派在编译期进行,只根据宗量的静态类型进行判断,在编译完成后不会发生改变。也可称为「编译期多态」

动态分派

动态分派在运行期进行,根据宗量的动态类型来判断需要调用哪个方法。也称「运行时多态」。

多重分派

虽然说 multiple 是「多重」的意思,但是实际上也可称为「双重分派」。

多重分派就是同时根据两种宗量的类型进行判断。单重分派就是只根据其中一种宗量进行判断。所以其实这两组词是从两个不同的角度来描述分派的类型,并不冲突。

Java 中的分派机制

在网上很多博客说 Java 中是单重分派;也有人说 Java 中实际上是静态双重分派,动态单重分派。下面是我的推论和总结。

Java 中的重载机制,是静态分派,其根据方法传入参数的静态类型进行判断,而方法的接收者,也直接在编译期就确定。所以是静态的双重分派。

而重写机制,肯定是动态分派了。但是其只根据方法接收者的动态类型进行判断。并不会根据方法参数的动态类型进行判断。所以是动态的单重分派。

那如果一个方法同时被重写和重载了呢,那这个方法属于什么分派机制呢?我认为,重写和重载本来就分别是编译期和运行期的多态机制,就是分开进行讨论的,在程序不同生命周期时间段的分派机制直接分开讨论并进行定义就好了。

访问者模式与伪双重分派

虽然说 Java 在编译期表现出了多重分派,但是也只能定性为单重分派语言,而这在设计代码时就会引起一些不便:在调用方法时无法根据参数的动态类型来分派对应的方法。所以我们只能曲线救国,可以通过以下两种方式实现伪动态双重分派。

1. instanceof

在方法内部可以通过 Java 的 instanceof 关键字来判断对象的动态类型。通过分支选择来进行对应的处理操作。但是这种思路会让处理代码变得十分臃肿,而且违背了「开闭原则」。每次要新增一个被处理类时,就要修改代码新增一个分支进行处理。具体的实现方法很简单,不赘述。

2. 访问者模式

设计模式中的「访问者模式」就是通过利用伪双重分派来实现的。主要思路如下:

设调用方为「类 A 」,被调用方为「类 B 」。通过反转调用方和被调用方,让类 A 重写方法来调用类 B 的重载方法。通过 this 变量将类 A 的动态类型传递给类 B 的重载方法,实现了伪双重分派。

在下面这个例子中,我们想让 Player.play() 方法根据 Instrument 的动态类型来分别调用对应的重载方法,从而进行不同的处理:

class Player {
void play(Instrument instrument) {
System.out.println("instrument notes");
} void play(Violin violin) {
System.out.println("player starts playing violin");
violin.note();
} void play(Piano piano) {
System.out.println("player starts playing piano");
piano.note();
}
} class Instrument {
void note() {
System.out.println("instrument notes");
} void accept(Player player) {
player.play(this);
}
} class Violin extends Instrument {
@Override
public void note() {
System.out.println("violin notes");
} @Override
public void accept(Player player) {
player.play(this);
}
} class Piano extends Instrument { @Override
public void note() {
System.out.println("piano notes");
} @Override
public void accept(Player player) {
player.play(this);
}
} public class Main { public static void main(String[] args) {
Player player = new Player();
System.out.println("-------双重分派-------");
Instrument[] instruments = {new Violin(), new Piano()};
for (Instrument instrument : instruments) {
instrument.accept(player);
} System.out.println("-------单重分派-------");
for (Instrument instrument : instruments) {
player.play(instrument);
}
}
}
// 结果
/*
-------双重分派-------
player starts playing violin
violin notes
player starts playing piano
piano notes
-------单重分派-------
instrument notes
instrument notes
*/

从上方的代码运行结果可以得知,在调用重载方法时,不会根据对象的动态类型进行判断,而是根据静态类型判断,只会调用 play(Instrument) 方法。当然,我们在重载 Player 的方法时可以直接调用 Instrument.note() 方法,让编译器去调用子类重写后的方法。

但是使用双重分派的意义在于, 把具体的处理逻辑放在调用者 Player 类的方法中,减少对 Instrument 的实现类的修改。事实上 Instrument 的子类只需要实现一个 accept(Player) 方法即可。

访问者模式的出现基本上也就是因为 Java 这类语言的单重分派特性吧。如果是多重分派的语言,就可以直接利用语言特性了。

参考文章:

  1. 面向对象语言的多分派、单分派、双重分派

  2. What is dispatching in JAVA?

多重分派(multiple dispatch)与访问者模式的更多相关文章

  1. 设计模式学习-使用go实现访问者模式

    访问者模式 定义 优点 缺点 适用范围 代码实现 什么是 Double Dispatch 参考 访问者模式 定义 访问者模式(Visitor):表示一个作用于某对象结构中的各元素的操作.它使你可以在不 ...

  2. C#设计模式总结 C#设计模式(22)——访问者模式(Vistor Pattern) C#设计模式总结 .NET Core launch.json 简介 利用Bootstrap Paginator插件和knockout.js完成分页功能 图片在线裁剪和图片上传总结 循序渐进学.Net Core Web Api开发系列【2】:利用Swagger调试WebApi

    C#设计模式总结 一. 设计原则 使用设计模式的根本原因是适应变化,提高代码复用率,使软件更具有可维护性和可扩展性.并且,在进行设计的时候,也需要遵循以下几个原则:单一职责原则.开放封闭原则.里氏代替 ...

  3. JAVA设计模式之访问者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

  4. C#设计模式——访问者模式(Visitor Pattern)

    一.概述由于需求的改变,某些类常常需要增加新的功能,但由于种种原因这些类层次必须保持稳定,不允许开发人员随意修改.对此,访问者模式可以在不更改类层次结构的前提下透明的为各个类动态添加新的功能.二.访问 ...

  5. Java实现在访问者模式中使用反射

    集合类型在面向对象编程中很常用,这也带来一些代码相关的问题.比如,“怎么操作集合中不同类型的对象?” 一种做法就是遍历集合中的每个元素,然后根据它的类型而做具体的操作.这会很复杂,尤其当你不知道集合中 ...

  6. 设计模式《JAVA与模式》之访问者模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

  7. Java 设计模式系列(二三)访问者模式(Vistor)

    Java 设计模式系列(二三)访问者模式(Vistor) 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要修改的话,接受这个操作的数据结构则可以 ...

  8. Java设计模式—访问者模式

    原文地址:http://www.cnblogs.com/java-my-life/archive/2012/06/14/2545381.html 总结的太棒啦,导致自己看了都不想总结了...... 在 ...

  9. 《JAVA设计模式》之访问者模式(Visitor)

    在阎宏博士的<JAVA与模式>一书中开头是这样描述访问者(Visitor)模式的: 访问者模式是对象的行为模式.访问者模式的目的是封装一些施加于某种数据结构元素之上的操作.一旦这些操作需要 ...

随机推荐

  1. Node.js躬行记(19)——KOA源码分析(上)

    本次分析的KOA版本是2.13.1,它非常轻量,诸如路由.模板等功能默认都不提供,需要自己引入相关的中间件. 源码的目录结构比较简单,主要分为3部分,__tests__,lib和docs,从名称中就可 ...

  2. 为 map 中不存在的 key 提供缺省值

    需求 需要往一个复杂的 map 中写入数据,或为 map 中不存在 key 提供 default value java standard library Map<K, List<V> ...

  3. strlen获取字符数组为什么是255

    为什么是255呢? strlen函数的规则是,读取到0则判断字符串结束. char为1字节,只有8位. 所以...... -1就是 1111 1111, -2就是 1111 1110, 直到-128: ...

  4. C# .NET ML.NET 机器学习 图像分类

    一. 准备工作 IDE是 VS2019.先下载好"resnet_v2_50_299.meta"这个文件,放入"C:\Users\jk\AppData\Local\Temp ...

  5. 第06组 Alpha冲刺 (2/6)

    目录 1.1 基本情况 1.2 冲刺概况汇报 1.郝雷明 2. 方梓涵 3. 黄少丹 4. 董翔云 5.曾丽莉 6. 詹鑫冰 7.鲍凌函 8.杜筱 9.曹兰英 10. 吴沅静 1.3 冲刺成果展示 1 ...

  6. 测试平台系列(94) 前置条件该怎么支持Python呢

    回顾 上一节我们狠狠操练了一番oss,但我们的任务还很长久,所以我们需要继续打磨我们的功能. 那今天就让我们来思考下,如何在前置条件支持python脚本,多的不说,我们也暂时不考虑其他语言,因为光考虑 ...

  7. Go微服务框架go-kratos实战01:quickstart 快速开始

    先来感受下用 kratos 快速创建项目 一.环境准备 1.1 安装依赖软件 protoc protoc-gen-go 建议开启 GO111MODULE 1.2 安装 kratos cli go in ...

  8. 面试官:Netty心跳检测机制是什么,怎么自定义检测间隔时间?

    哈喽!大家好,我是小奇,一位热爱分享的程序员 小奇打算以轻松幽默的对话方式来分享一些技术,如果你觉得通过小奇的文章学到了东西,那就给小奇一个赞吧 文章持续更新 一.前言 书接上回,昨天在地里干了一天的 ...

  9. Spring IOC源码研究笔记(2)——ApplicationContext系列

    1. Spring IOC源码研究笔记(2)--ApplicationContext系列 1.1. 继承关系 非web环境下,一般来说常用的就两类ApplicationContext: 配置形式为XM ...

  10. django框架6

    内容概要 神奇的双下划线查询 外键字段的创建 外键字段操作 多表查询 基于对象的跨表查询 基于双下划线的跨表查询 双下线查询扩展 如何查看SQL语句 内容详情 神奇的双下划线查询 1.查询年龄大于20 ...