一、访问者模式的核心思想
访问者模式的核心思想:操作改变,数据结构不变。访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类或类群(即被访问者)增加新的操作(即访问者)而无须为它们进行任何修改。
访问者模式的组成结构包括如下。
- 访问者接口Visitor:为该对象结构中具体访问者定义一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者,这样访问者就可以通过该元素的特定接口直接访问它。
- 访问者实现类MyVisitor:实现每个由访问者接口Visitor声明的操作,实现对被访问者的调用。
- 被访问者接口Subject:定义一个accept操作,它以一个访问者为参数,通过accept(Visitor)方法接受 Visitor 的访问。
- 被访问者实现类 MySubject:实现由Subject 提供的accept操作。
访问者模式的结构图如下图
下面来看具体的实现
(1) 访问者接口 Visitor.java定义了一个 visit()接口,它用来访问目标类 Subject。
package behavior.visitor;
/**
* @author Minggg
* 访问者接口
*/
public interface Visitor {
public void visit(Subject subject);
}
(2) 访问者实现类 MyVisitor.java执行访问目标类的动作,调用目标类的具体函数 getSubject()实现访问。
package behavior.visitor;
/**
* @author Minggg
* 访问者实现类
*/
public class MyVisitor implements Visitor {
public void visit(Subject subject){
System.out.println("访问了主题:"+subject.getSubject());
}
}
(3) 目标类接口 Subject.java提供了为自身增加访问者的接口 accept(),并提供自身的操作函数getSubject()。
package behavior.visitor;
/**
* @author Minggg
* 目标类接口
*/
public interface Subject {
public void accept(Visitor visitor);
public String getSubject();
}
(4) 目标类 MySubject.java 实现 accept()方法,实现 Visitor 对自身 this 的访问。
package behavior.visitor;
/**
* @author Minggg
* 目标类
*/
public class MySubject implements Subject {
public void accept(Visitor visitor) {
visitor.visit(this);
}
public String getSubject() {
return "这是一个主题";
}
}
从以上 Visitors 和 Subject 之间的关系来看,它们两者之间相互依赖:
- Visitor 拥有一个访问 Subject 对象的方法 visit(Subject subject),是主动方。
- Subject 拥有一个接受 Visitor 访问的方法 accept(Visitor visitor),是被动方;并且在接受访问时实现对 visit()的调用,即实现了访问。
因此,要实现 Visitors 对 Subiect 的访问,首先应该允许 Subject接受 Visitor 的访问,然后即可实现对自身的访问。使用的方法如下:首先创建MyVisitor和MySubject 对象,然后调用 accept()方法即可实现接受访问。
package behavior.visitor;
public class Test {
public static void main(String[] args) {
Visitor visitor = new MyVisitor();
Subject subject = new MySubject();
subject.accept(visitor);
}
}
运行该程序输出如下信息:
访问了主题:这是一个主题
二、何时使用访问者模式
要将现有的类层次结构扩展实现新的功能,一般的方法是给类添加新的方法,但是这种方法存在3个问题:
- 新功能和现有对象模型不兼容。
- 类层次结构设计人员可能无法预知以后开发过程中将会需要哪些功能。
- 如果已有的类层次结构不允许修改代码,则无法进行扩展。
此时最好的方法就是在类层次结构设计中使用访问者模式。
访问者模式适用于数据结构相对稳定的系统,它把数据结构和作用于结构上的操作之间的耦合解脱开,使得操作集合可以相对自由地演化。数据结构的每一个节点都可以接受一个访问者的调用,此节点向访问者对象传入节点对象,而访问者对象则反过来执行节点对象的操作,这样的过程叫做双重分派。节点调用访问者,将它自己传入,访问者则将某算法针对此节点执行。双重分派意味着施加于节点之上的操作是基于访问者和节点本身的数据类型,而不仅仅是其中的一者。
当一个应用满足以下条件时,我们可以使用访问者模式:
- 一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。访问者模式使得你可以将相关的操作集中起来定义在一个类中。
- 当该对象结构被很多应用共享时,用访问者模式让每个应用仅包含需要用到的操作。
- 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
使用访问者模式的优点:
- 易于添加那些目前尚未考虑到的方法。这也是使用访问者的原因–扩展功能
- 可以使类更加小巧,因为那些很少使用的方法,可以在外部定义。意味着如果一个方法经常使用,最好定义在类中,当然在第一次定义中没有考虑到此方法除外。
使用访问者模式的缺点:
- 访问者角色不适合具体元素角色经常发生变化的情况。例如增加新具体元素类,访问者接口就需要改变了。
- 访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来这就破坏了元素角色的封装性。访问者和被访问的对象的合性很大。
- 元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。因为访问者不能直接访问元素的私有数据。
三、Java中的应用–Java Annotaion 访问者实现
在 Java Annotations 注释符中,AnnotationValue 表示注释类型元素的值,存储了不同的数据结构。该值是以下类型之一:
- 基本类型的包装类(例如Integer)
- String
- TypeMirror
- VariableElement(表示一个枚举常量)
- AnnotationMirror
- List<? extends AnnotationValue>(如果值是一个数组,则表示按声明顺序排列的元素)
AnnotationValue 中的数据结构可能是变化的,因此为了适应这种变化,Java6就为它提供了访问者类来添加不同的实现。访问者共包括3个类:
- AnnotationValueVisitor 是注释类型元素值 AnnotationValue 的 visitor,使用 visitor 设计模式的变体。标准 visitor基于类型层次结构成员的具体类型进行调度,与它不同,此visitor 基于存储数据的类型进行调度;对于存储,没有不同的子类,例如boolean值和int值。在编译时某个值的类型未知时,实现此接口的类被用来对该值进行操作。在将visitor 传递给某个值的 accept 方法时,适用于该值的visitXYz方法将被调用。
- AbstractAnnotationValueVisitor 是 AnnotationValueVisitor 的抽象实现,它可以添加一些方法以适应添加到 Java编程语言未来版本中的新语言结构。所以,将来可能会向此类添加名称以 visit 开头的方法;为了避免不兼容,扩展此类的类不应该声明任何名称以 visit 开头的实例方法。
- SimpleAnnotationValueVisitor6 则是Java6版本的具体访问者实现。
它们之间的关系如图所示
该图完全符合设计者模式的结构。