学了那么久的Java,你是否知道Java是属于单分派语言还是双分派语言?什么?单分派和双分派是什么意思还不知道?了解了分派机制,就能明白访问者模式的前世今生了。
访问者模式是23种设计模式当中比较少见少用的一种,相比于其他常见的,如单例、工厂、观察者、代理等模式,理解起来要稍微费劲一点。但,如果从访问者模式的产生由来去思考和理解,或许会更容易,那为什么会出现访问者这一种设计模式呢?首先先要理解什么是单分派和双分派。
分派
分派是什么样的概念呢?分派即Dispatch,在面向对象编程语言中,我们可以把方法调用理解为一种消息传递(Dispatch)。一个对象调用另一个对象的方法,相当于给被调用对象发送一个消息,这个消息包括对象名、方法名、方法参数等信息。
单分派
单分派,即执行哪个对象的方法,根据对象的运行时类型决定;执行对象的哪个方法,根据方法参数的编译时类型决定。
双分派
双分派,即执行哪个对象的方法,根据对象的运行时类型来决定;执行对象的哪个方法,根据方法参数的运行时的类型来决定。
听起来似乎很绕口,其实很简单,下面以表格的形式展示,可能会更加明了一点。
分派机制如果在是在编程语言中,单分派和双分派就是跟多态和函数重载直接相关。那Java是属于单分派还是双分派呢?我们直接用个代码示例在验证好了。
public class Parent {
public void call() {
System.out.println("I'm Parent.");
}
}
public class Child extends Parent {
public void call() {
System.out.println("I'm Child.");
}
}
public class App {
public void call(Parent parent) {
parent.call();
}
public void sayName(Parent parent) {
System.out.println("saveName重载,参数类型: Parent");
}
public void sayName(Child child) {
System.out.println("saveName重载,参数类型: Child");
}
public static void main(String[] args) {
App app = new App();
Parent obj = new Child();
app.call(obj);
app.sayName(obj);
}
}
你觉得上述代码的执行结果输出是什么呢?如下:
I'm Child.
saveName重载,参数类型: Parent
Process finished with exit code 0
简单分析下:
new对象时,我们new的是Child对象,通过call方法的调用结果可知,最终打印出的是I'm Child.
,即App类的call方法中,到底调用Parent的call还是会调用Child的call,由我们创建的对象实例类型决定,是运行时决定的,正所谓多态;而通过saveName方法的调用结果可知,尽管我们new的是Child对象,结果调用的却是形参类型是Parent的那个重载,由此可知,这里决定调用那个saveName重载,在编译时就决定了。所以,很明显了,Java支持单分派,不支持多分派。
也因此得知,Java语言中的函数重载,并不是在运行时,根据传递给函数的参数的实际类型,来决定调用哪个重载函数,而是在编译时,根据传递给函数的参数的声明类型,来决定调用哪个重载函数。即Java语言中,多态是一种动态绑定,函数重载是一种静态绑定。
访问者模式的由来
正因为在某些编程语言中,如Java不支持双分派,所以访问者设计模式诞生了。何出此言?我们先来看一个业务功能场景。
假设有一个图像视频存储服务器,这个服务器需要针对用户上传的图像、视频进行合法性校验,如不能涉及黄色内容,也是鉴黄拦截处理。而图像视频有多种封装格式,如有图片jpg、png,动图gif,视频mp4、avi、flv等,然后不同类型的文件,提取内容的方式也不一样,下面是针对这个功能场景的一个代码设计。
如果你有一定的代码架构设计基础,相信一开始你也能想到可以利用多态特性来实现比较友好的封装抽象设计,如下:
#基本数据类
/**
* 媒体文件基类
*/
public abstract class MediaFile {
protected String filePath;
public MediaFile(String filePath) {
this.filePath = filePath;
}
}
/**
* 假设这个Image类是从不同媒体文件中提取出来的对象,
* 里面存储图像鉴别的相关内容和信息,此处省略相关变量和方法
*/
public class Image {
}
/**
* 该类代表静态图片,如jpg、png
*/
public class Picture extends MediaFile {
public Picture(String filePath) {
super(filePath