以前看过大话设计模式,对各种模式有过一点了解,可是没怎么用,可没怎么深入去了解所以忘得很快,现在沉下心来好好研究下。但是,在看到访问者模式的时候,我对accept存在的必要性很是不理解,觉得为何不直接调用visitor的visit方法去访问 Element,不更直接,而且去除双向依赖,也是一个好的设计。于是百度搜索了N久,看到一篇文章,然后亲自测试,终于领会到accept存在的必要性,在此分享出来,供有意或者参考。
给我启发的文章链接:访问者模式讨论篇:java的动态绑定与双分派
以下是我的验证代码:
Element类:
package com.lh.test; public abstract class Element { public abstract void accept(IVisitor visitor); }
public class ConcreteElementA extends Element { @Override public void accept(IVisitor visitor) { System.out.println("--ConcreteElementA-- 的方法--accept--调用"); visitor.visit(this); } public void myfuncA(){ System.out.println("--ConcreteElementA-- 的方法--myfuncA--调用"); } }
public class ConcreteElementB extends Element { @Override public void accept(IVisitor visitor) { System.out.println("--ConcreteElementB-- 的方法--accept--调用"); visitor.visit(this); } public void myfuncB(){ System.out.println("--ConcreteElementB-- 的方法--myfuncB--调用"); } }
IVisitor :注意 visit(Element element) 这个方法,他会让你知道 accept 双重分派的必要性,采用模板方法模式
public abstract class IVisitor { public void visit(Element element){ System.out.println("我在处理---Element---对象"); } public abstract void visit(ConcreteElementA elementA); public abstract void visit(ConcreteElementB elementB); }
public class MyVisitor extends IVisitor { @Override public void visit(ConcreteElementA elementA) { System.out.println("我在处理---Element-A--对象"); elementA.myfuncA(); } @Override public void visit(ConcreteElementB elementB) { System.out.println("我在处理---Element-B--对象"); elementB.myfuncB(); } }
public class MyVisitorB extends IVisitor { @Override public void visit(ConcreteElementA elementA) { System.out.println("--B--在处理---Element-A--对象"); elementA.myfuncA(); } @Override public void visit(ConcreteElementB elementB) { System.out.println("--B--在处理---Element-B--对象"); elementB.myfuncB(); } @Override public void visit(Element element) { System.out.println("--B--在处理---Element--对象"); } }
测试流程:
ConcreteElementA elementA=new ConcreteElementA(); ConcreteElementB elementB=new ConcreteElementB(); IVisitor visitor=new MyVisitor(); System.out.println("=========静态分派========="); visitor.visit(elementA); visitor.visit(elementB); System.out.println("=========静态分派=向上转型后========"); Element element=elementA; visitor.visit(element); element=elementB; visitor.visit(element); System.out.println("=========动态分派========="); elementA.accept(visitor); elementB.accept(visitor); System.out.println("=================="); System.out.println("======*******======="); visitor=new MyVisitorB(); System.out.println("=========静态分派========="); visitor.visit(elementA); visitor.visit(elementB); System.out.println("=========静态分派=向上转型后========"); element=elementA; visitor.visit(element); element=elementB; visitor.visit(element); System.out.println("=========动态分派========="); elementA.accept(visitor); elementB.accept(visitor); System.out.println("==================");
=========静态分派========= 我在处理---Element-A--对象 --ConcreteElementA-- 的方法--myfuncA--调用 我在处理---Element-B--对象 --ConcreteElementB-- 的方法--myfuncB--调用 =========静态分派=向上转型后======== 我在处理---Element---对象 我在处理---Element---对象 =========动态分派========= --ConcreteElementA-- 的方法--accept--调用 我在处理---Element-A--对象 --ConcreteElementA-- 的方法--myfuncA--调用 --ConcreteElementB-- 的方法--accept--调用 我在处理---Element-B--对象 --ConcreteElementB-- 的方法--myfuncB--调用 ================== ======*******======= =========静态分派========= --B--在处理---Element-A--对象 --ConcreteElementA-- 的方法--myfuncA--调用 --B--在处理---Element-B--对象 --ConcreteElementB-- 的方法--myfuncB--调用 =========静态分派=向上转型后======== --B--在处理---Element--对象 --B--在处理---Element--对象 =========动态分派========= --ConcreteElementA-- 的方法--accept--调用 --B--在处理---Element-A--对象 --ConcreteElementA-- 的方法--myfuncA--调用 --ConcreteElementB-- 的方法--accept--调用 --B--在处理---Element-B--对象 --ConcreteElementB-- 的方法--myfuncB--调用 ==================
说明:
方法的重载是静态多态,编译期就已经确定执行哪一个方法,自继承的方法重写 是动态多态,运行时调用的它实际类型的方法。
所以,可以看到,如果直接visit ConcreteElementA、ConcreteElementB,结果与期望的一致。但是,如果对 ConcreteElementA、ConcreteElementB上转型到 Element,然后再调用,就会出现期望之外的结果,这就是 方法重载静态分派的结果,他只认当前对象的当前类型。
而继承多态,会在调用方法是,找到他实际类型所对应的方法,如MyVisitorB 上转型为IVisitor,但在调用 visit(Element element) 这个方法时,他会找到当前对象,实际类型的方法(重写后的方法),让后在参数的选择上,却只认当前类型,所以有“--B--在处理---Element--对象” 这样的情况。
而 访问者模式中 accept方法,就是先运用 继承的动态分派找到 Element对象的实际类型,然后 把当前Element对象的实际类型作为参数 直接给 visitor的visit()方法,以确定实际的正确的参数类型。
如果直接使用visitor visit,那么在使用的时候,必须清楚的知道该元素的类型,不能通过向上转型的多态去确定实际类型,如IVisitor 中,若没有 visit(Element element) 这个方法的存在,在数据集中读取到一系列的Element 对象后,却无法直接使用visitor.visit()方法,必须清楚的知道这些Element 的具体类型。
所以,多态有他的好处和方便,但是对于方法重载的多态问题,还是得小心留意些。双重分派实现的动态分派可以很好的解决这个问题,也可避免instanceof类型判断的使用。