首页 > 代码库 > 设计模式学习笔记--访问者(Visitor)模式

设计模式学习笔记--访问者(Visitor)模式


写在模式学习之前


       什么是设计模式:在我们进行程序设计时,逐渐形成了一些典型问题和问题的解决方案,这就是软件模式;每一个模式描述了一个在我们程序设计中经常发生的问题,以及该问题的解决方案;当我们碰到模式所描述的问题,就可以直接用相应的解决方法去解决这个问题,这就是设计模式。

       设计模式就是抽象出来的东西,它不是学出来的,是用出来的;或许你根本不知道任何模式,不考虑任何模式,却写着最优秀的代码,即使以“模式专家”的角度来看,都是最佳的设计,不得不说是“最佳的模式实践”,这是因为你积累了很多的实践经验,知道“在什么场合代码应该怎么写”,这本身就是设计模式。

       有人说:“水平没到,学也白学,水平到了,无师自通”。诚然,模式背熟,依然可能写不出好代码,更别说设计出好框架;OOP理解及实践经验到达一定水平,同时也意味着总结了很多好的设计经验,但"无师自通",却也未必尽然,或者可以说,恰恰是在水平和经验的基础上,到了该系统的学习一下“模式”的时候了,学习一下专家总结的结果,印证一下自己的不足,对于提高水平还是很有帮助的。

       本系列的设计模式学习笔记,实际是对于《Java与模式》这本书的学习记录。


分派(Dispatch)


变量被声明时的类型叫做变量的静态类型,变量所引用的真实类型叫做变量的实际类型。

根据对象的类型而对方法进行选择,就是分派。分派是面向对象语言所提供的关键特性之一。


静态分派和动态分派


根据分派发生的时期,可以分为静态分派和动态分派。静态分派发生在编译器,一般通过overload实现;动态分派发生在运行期,一般通过override实现,动态分派产生了多态性。

以下分析一个代码例子:

String s1 = "bc";
Object o = s1 + "c";
String s2 = "abc";

Boolean b = o.equals(s2);

o.equals(s2),这行代码,实际是调用的o所代表的实际类型String的equals方法,所以b=true。


单分派和多分派


一个方法所属的对象,叫做方法的接收者,方法的接收者和参数统称做方法的宗量。

根据分派基于多少种宗量,可以将面向对象的语言分为单分派(Uni-Dispatch)语言和多分派(Multi-Dispatch)语言。

C++和Java,既是静态的多分派,又是动态的单分派;也就是说,编译期间有Override,既考虑接受者,又考虑参数,是多分派,运行期间,只考虑接收者,是单分派。


双重分派


一个方法根据两个宗量的类型,来决定执行的方法,叫做双重分派或双分派。

Java不支持动态的多分派,也就意味着Java不支持动态的双分派,但是可以通过设计模式,实际动态的双分派。

有两种方法:类型判断和返传球。类型判断是条件分支判断,在JavaApi的UI组件中比较常见;返传球的这种实现方式,就是我们接下来将要讨论的访问者(Visitor)模式。


访问者模式的定义


访问者模式的目的是封装一些施加于某种数据元素结构之上的操作。

访问者模式适用于数据结构相对固定的系统,它把数据结构和作用于数据结构上的操作之间的耦合解脱开,使得操作集合可以相对自由的演化。

数据结构的每一个节点,都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作。这样的过程叫做双重分派。节点调用访问者,将它自己传入,访问者则将某算法针对此节点来执行。

双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。


访问者模式的结构


结构图




所涉及的角色


(1)抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口。

(2)具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口。

(3)抽象节点(Node)角色:声明一个接受操作,接受一个访问者对象作为参量。

(4)具体节点(ConcreteNode)角色:实现了抽象节点所规定的接受操作。

(5)结构对象(ObjectStructure)角色:可以遍历结构中的所有元素。


代码实现


import java.util.Enumeration;
import java.util.Vector;

public class VisitorTest {

	private static ObjectStructure aObjects;
	private static Visitor visitor;
	
	public static void main(String[] args)
	{
		//创造一个结构对象
		aObjects = new ObjectStructure();
		//给结构增加一个NodeA类型节点
		aObjects.add(new NodeA());
		//给结构增加一个NodeB类型节点
		aObjects.add(new NodeB());
		//创建一个新德访问者
		visitor = new VisitorA();
		//让访问者访问结构
		aObjects.Action(visitor);
	}
}

interface Visitor {
	//对于NodeA的访问操作
	void Visit(NodeA node);
	//对于NodeB的访问操作
	void Visit(NodeB node);
}

class VisitorA implements Visitor {
	@Override
	public void Visit(NodeA nodeA) {
		System.out.println(nodeA.operationA());
	}
	@Override
	public void Visit(NodeB nodeB) {
		System.out.println(nodeB.operationB());
	}	
}

class VisitorB implements Visitor {
	@Override
	public void Visit(NodeA nodeA) {
		System.out.println(nodeA.operationA());
	}
	@Override
	public void Visit(NodeB nodeB) {
		System.out.println(nodeB.operationB());
	}	
}

abstract class Node {	
	//接受操作
	public abstract void accept(Visitor visitor);
}

class NodeA extends Node {
	@Override
	public void accept(Visitor visitor) {
		visitor.Visit(this);
	}
	public String operationA() { return "NodeA is visited";}
}

class NodeB extends Node {
	@Override
	public void accept(Visitor visitor) {
		visitor.Visit(this);
	}
	public String operationB() { return "NodeB is visited";}
}

@SuppressWarnings({"rawtypes","unchecked"})
class ObjectStructure {
	private Vector nodes;
	private Node node;
	
	public ObjectStructure() {nodes = new Vector();}
	public void add(Node node) {nodes.addElement(node);}
	
	/**
	 * 执行访问操作
	 * @param visitor
	 */
	public void Action(Visitor visitor)
	{
		for(Enumeration e = nodes.elements();e.hasMoreElements();)
		{
			node = (Node)e.nextElement();
			node.accept(visitor);
		}
	}
}

访问者模式的适用场景


(1)倾斜的可扩展性。访问者模式仅应当在被访问的类结构非常稳定的情况下使用。换言之,系统很少需要加入新节点的情况。

(2)算法众多,而数据结构的场景,可以使用访问者模式,实际这个第二条又把第一条说了一遍:)


访问者模式的优点和缺点


优点:

(1)访问者模式使得增加新的操作变得容易。

(2)访问者模式将有关行为集中到一个访问者对象中,而不是分散到一个个的节点类中。

(3)访问者模式可以跨过几个类的等级结构访问属于不同的等级结构的成员类。迭代模式只能访问同一等级结构的对象,而访问者模式能访问不同等级结构的对象。

(4)积累状态。每一个单独的访问者对象都集中了相关的行为,从而也就可以在访问过程中把执行操作的状态积累在自己内部,而不是分散到很多节点对象中。这是有益于系统维护的优点。

缺点:

(1)增加新的节点类很困难。每增加一个新德节点类,都意味着要在抽象访问者角色中增加一个新的抽血操作,并在每一个具体访问者类中增加相应的具体操作。

(2)破坏封装。访问者模式要求访问者对象访问并调用每一个节点对象的操作,这隐含了一个对所有节点对象的要求:它们必须暴漏一些操作和内部状态。不然,访问者的访问就变得没有意义。由于访问者对象自己会积累访问操作所需的状态,从而使这些状态不再存储在节点对象中,这也是破坏封装的。


由于存在明显的缺点,这也是一个有争议的设计模式。有人强调优点,有人反对使用。我们需要酌情判断,谨慎使用。

设计模式学习笔记--访问者(Visitor)模式