访问者模式 双重分派分析 与 accept方法存在的必要性

以前看过大话设计模式,对各种模式有过一点了解,可是没怎么用,可没怎么深入去了解所以忘得很快,现在沉下心来好好研究下。但是,在看到访问者模式的时候,我对accept存在的必要性很是不理解,觉得为何不直接调用visitor的visit方法去访问 Element,不更直接,而且去除双向依赖,也是一个好的设计。于是百度搜索了N久,看到一篇文章,然后亲自测试,终于领会到accept存在的必要性,在此分享出来,供有意或者参考。

给我启发的文章链接:访问者模式讨论篇:java的动态绑定与双分派

以下是我的验证代码:

Element类:

package com.lh.test;

public abstract class Element {

	public abstract void accept(IVisitor visitor);
}

Element A派生:

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--调用");
	}
}


Element B派生:

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);
}

MyVisitor:

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();
	}

}

MyVisitorB:对父类的已实现方法进行了重写

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类型判断的使用。

你可能感兴趣的:(访问者模式 双重分派分析 与 accept方法存在的必要性)