双重分发(Double Dispatch)
什么是双重分发?
谈起面向对象的程序设计时, 常说起的面向对象的多态, 其中关于多态, 经常有一个说法是父类引用指向子类对象.
这种父类的引用指向子类对象的写法类似下面这种:
- Animal animal = new Dog();
- animal.bark();
另一种常用的形式是
- public class Keeper {
- public void say(Animal a) {
- System.out.println("Animal say");
- }
- public void say(Dog dog) {
- System.out.println("dog say");
- }
- }
- Animal animal = new Animal();
- Animal dog = new Dog();
- Keeper keeper = new Keeper();
- keeper.say(animal);
- keep.say(dog);
那上面的 keeper 调用两次 say 的时候, 会输出什么内容呢? 会调用到两个不同的方法吗?
实际上在这两次调用的时候, 都会调用到 say(Animal a)这个方法. 由于这些内容在编译期就能确实下来, 这就是 Java 的 静态分发.
从上面的图我们看到, 对于两次调用生成的字节码, 确实都指向了 say(Animal a)这个方法, 运行时直接执行方法, 输出了对应的内容.
那对应的 animal.bark() 为什么最终会调用到 dog 类的方法? 这是在运行时确定具体的方法接收者的类型并执行. 这就是所谓的动态分发, 在运行时确定具体的方法, 实现面向对象的多态.
分发(Dispatch)
分发就是指最终确定一个要执行的方法的过程.
对于 Java 等静态语言来说, 都是通过 单一分发 (Single Dispatch) 来进行的方法执行.
比如这样一行代码
dog.eat(new Bone())
最终执行要选择的 eat 方法, 只会根据 dog 的具体类型选择到对应的方法, 而传入的参数并不能影响到对应方法的选择, 这种就是 single Dispatch
为了让传入的真实参数, 这里就是 Bone 来真正起到作用, 就需要用到 Double Dispatch 或者叫做 Multiple Dispatch
也就是说最终决定调用方法是哪一个的, 不仅仅是方法的接收者, 还受参数类型的决定.
Visitor 模式
在 GoF 的设计模式中, Visitor 模式就使用到了 Double Dispatch 达到了调用真实对象的目的.
对于 Visitor 模式, 最常用的例子是树的遍历. 比如在处理到节点和树叶时的方式有区别, 此归通过 visitor 的双重分发, 实现对于不同的 Element , 执行不同的内容.
代码类似这样:
- node.accept(new ConcreateVisitor());
- leaf.accept(new ConcreateVisitor());
node 中的 accept 方法, 会将自己的真实类型再次传递回 visitor
- public void accept(Visitor v) {
- v.visit(this);
- }
此时, 在 visitor 中, 就能根据真实的类型来调用具体的方法, 对应 node 和 leaf 分别有类似这样的方法:
- public void visit(Node n);
- public void visit(Leaf l);
Visitor 总结起来一般是包含 visitor 接口, 在 visitor 接口中, 包含各个即将被访问的 Element 对象的处理逻辑. 在 各个 Element 的具体实现中, 再将自己的类型传递回 visitor 进行二次分发, 实现确切逻辑的调用.
在 Tomcat 中的应用
Visitor 在 Tomcat 中也有应用, 典型的是解析 EL 表达式.
比如 org.apache.el.parser.Node
这个类中包含一个 accept(NodeVisitor visitor)方法
实际的 Node 类型有很多, 但在真实调用的这个时候, 会通过
- public void accept(NodeVisitor visitor) throws Exception {
- visitor.visit(this);
将真实类型传回 visitor, vistor 中会调用具体的方法, 从而实现参数也能起到决定作用的功能.
- public void visit(Node node) throws ELException {
- if (node instanceof AstFunction) {
- AstFunction funcNode = (AstFunction) node;
- Method m = null;
- } else if (xxx) {
- }
这里一般会声明多个 visit 方法, 然后上面的 visit(this)会直接定位到目标方法上.
以上就是 Java 中的各类分发, 以及 visitor 这种模式通过模式的形式来实现双重分发的效果.
来源: http://zhuanlan.51cto.com/art/201807/578177.htm