一、定义
GOF中,对Visitor模式的意图是这样描述的:表示一个作用于某对象结构中的各元素的操作。它使得你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
从描述来看,访问者模式主要用于扩展现有的类层次结构来实现新的行为。一般的扩展方法是添加新的方法以提供新的行为。但是有时候,新行为可能和现有对象根本就不兼容。还有可能,类层次的开发人员无法预知以后的开发过程中将会需要哪些功能。为了方便后来的开发者可以在软件开发过程中方便地扩展该类层次结构的行为,开发人员可以在设计中使用访问者模式。
二、应用场景
考虑一个编译器,它将源程序表示为一个抽象的语法树。编译器需要对树节点实施某些操作以进行“静态语意”分析。它可能要定义许多操作进行类型检查、代码优化、流程分析等等。这些操作大多要对不同的节点进行不同的处理。例如对代表赋值语句的节点的处理就不同于代表变量或算术表达式的节点的处理。注意,这里有两个维度的变化:编译器需要定义各种不同的操作,每种操作针对不同节点流程也不同。将这些操作分散到各种节点类中会导致整个系统难以理解、难以维护。不同种的操作如类型检查和流程分析代码放在一起,将产生混乱。若可以实现独立地增加新的操作,将相似的操作集中起来,并使得这些节点独立于作用其上的操作,无疑能使得系统变得更容易扩展。
可以将每一个类中的相关操作包装在一个独立的对象(称为一个Visitor)中,并在遍历抽象语法树时将这个对象传递给当前访问的元素。当一个元素“接受”该访问者时,该元素向访问者发送一个包含自身类信息的请求(将自己this作为参数传递给访问者)。然后访问者将为该元素执行相应操作。访问者将使用节点类提供的公共接口来访问节点对象的属性。
使用Visitor模式,需要定义两个类层次:一个对应于接受操作的元素(Element)层次,另一个对应于定义对元素操作的访问者(Visitor)层次。给访问者类层次增加一个新的子类即可创建一类新的操作。
三、类图
四、代码示例
package inode;
import visitor.*;
public interface INode {
int getFileNum();
void Add(INode node);
void Remove(INode node);
public void Accept(Visitor v);
}
INodeFile.java
package inode;
import visitor.Visitor;
public class INodeFile implements INode {
String name;
INodeFile(String name)
{
this.name=name;
}
@Override
public int getFileNum() {
return 1;
}
public String getFileName()
{
return this.name;
}
@Override
public void Add(INode node) {
System.out.println("File-----No subdirectory");
}
@Override
public void Remove(INode node) {
System.out.println("File-----No subdirectory");
}
@Override
public void Accept(Visitor v) { //double dispatch
v.visitINodeFile(this);
}
}
INodeDirectory.java
package inode;
import java.util.*;
import visitor.Visitor;
public class INodeDirectory implements INode {
private LinkedList<INode> list;
String name;
INodeDirectory(String name)
{
this.name=name;
list=new LinkedList<INode>();
}
@Override
public int getFileNum() {
int sum=0;
for(INode temp:list)
{
sum+=temp.getFileNum();
}
return sum;
}
public String getDirName()
{
return this.name;
}
@Override
public void Add(INode node) {
list.add(node);
}
@Override
public void Remove(INode node) {
list.remove(node);
}
@Override
public void Accept(Visitor v) {
v.visitINodeDirectory(this); //double dispatch
for(int i=0;i<list.size();i++)
{
list.get(i).Accept(v);
}
}
}
Visitor类层次结构
package visitor;
import inode.*;
public interface Visitor {
public void visitINodeFile(INodeFile file);
public void visitINodeDirectory(INodeDirectory dir);
}
package visitor;
import inode.INodeDirectory;
import inode.INodeFile;
public class NumberVisitor implements Visitor {
private int filenumber;
public NumberVisitor()
{
this.filenumber=0;
}
public int getAllNumber()
{
return this.filenumber;
}
@Override
public void visitINodeFile(INodeFile file) {
this.filenumber++;
}
@Override
public void visitINodeDirectory(INodeDirectory dir) {
this.filenumber++;
}
}
package visitor;
import inode.INodeDirectory;
import inode.INodeFile;
public class NameVisitor implements Visitor {
private StringBuilder name=null;
public NameVisitor()
{
this.name=new StringBuilder();
}
public String getAllname()
{
return name.toString();
}
@Override
public void visitINodeFile(INodeFile file) {
name.append("File: "+file.getFileName()+"|");
}
@Override
public void visitINodeDirectory(INodeDirectory dir) {
name.append("Directory: "+dir.getDirName()+"|");
}
}
package inode;
import visitor.*;
public class Client {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
INode plant=new INodeDirectory("plant");
INode root=new INodeDirectory("root");
INode music=new INodeFile("music");
INode pdf=new INodeFile("pdf");
INode movie=new INodeFile("movie");
plant.Add(music);
plant.Add(pdf);
plant.Add(movie);
INode book=new INodeFile("book");
root.Add(plant);
root.Add(book);
NumberVisitor numvis=new NumberVisitor();
root.Accept(numvis);
System.out.println("Total number: "+numvis.getAllNumber());
NameVisitor namevis=new NameVisitor();
root.Accept(namevis);
System.out.println("All Files: "+namevis.getAllname());
}
}