什么是访问者模式
将数据结构与处理分离开来。
比如一个人,他有自己的名字,他可以吃东西。这里的“人”就是一个数据结构,名字是属性,吃东西是行为方法(处理)。
public class Person {
private String name;
public void eat() {
System.out.println("自己吃东西");
}
}
将数据结构中的处理分离出去,就像这样换成一个accept方法,接收一个访问者,然后将自己作为参数,调用访问者的visit方法
public class Person {
private String name;
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
在访问者中,调用相应的visit方法,让访问者“帮我吃东西”
public class Visitor {
public void visit(Person person) {
System.out.println("观察者帮person吃");
}
}
另一个例子
在下面这个例子中,使用Composite模式中用到的那个文件和文件夹的例子作为访问者要访问的数据结构。
Visitor类,表示访问者的抽象类。访问者依赖于它所访问的数据结构(即File类和Directory类)
public abstract class Visitor {
public abstract void visit(File file);
public abstract void visit(Directory directory);
}
Element接口,只有一个accept方法,表示实现了这个接口的类,都接受访问者来访问自己(把本类的处理分离出去)。
public abstract class Element {
public abstract void accept(Visitor visitor);
}
Entry类,与Composite模式中的Entry类基本一致,表示抽象容器,用来实现File和Directory的一致性。
public abstract class Entry extends Element{
public abstract String getName();
public abstract int getSize();
public Entry add(Entry entry) throws FileTreatmentException {
throw new FileTreatmentException();
}
public Iterator iterator() throws FileTreatmentException {
throw new FileTreatmentException();
}
public String toString() {
return getName() + " (" + getSize() + ")";
}
}
File类,实现了accept方法,传入一个访问者,然后通过多态调用相应的visit方法,将this作为参数传给访问者。
public class File extends Entry {
private String name;
private int size;
public File(String name, int size) {
this.name = name;
this.size = size;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
return size;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
Directory类,也实现了accept方法,与File的做法一样。
public class Directory extends Entry {
private String name;
private ArrayList directory = new ArrayList();
public Directory(String name) {
this.name = name;
}
@Override
public String getName() {
return name;
}
@Override
public int getSize() {
int size = 0;
Iterator it = directory.iterator();
while (it.hasNext()) {
Entry entry = (Entry) it.next();
size += entry.getSize();
}
return size;
}
@Override
public Entry add(Entry entry) throws FileTreatmentException {
directory.add(entry);
return this;
}
@Override
public Iterator iterator() throws FileTreatmentException {
return directory.iterator();
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
ListVisitor类,是Visitor的子类,表示具体的访问者。
- visit(File file)方法是用来实现“对File类的实例要进行的处理”,这里只是简单的输出文件夹名。
- visit(Directory directory)方法实现了“对Directory类的实例要进行的处理”,这里会递归调用directory中的file和其中的子文件夹,即accept方法与visit方法之间的相互递归调用。
public class ListVisitor extends Visitor {
private String currentdir = "";
@Override
public void visit(File file) {
System.out.println(currentdir + "/" + file);
}
@Override
public void visit(Directory directory) {
System.out.println(currentdir + "/" + directory);
String saveDir = currentdir;
currentdir = currentdir + "/" + directory.getName();
Iterator iterator = directory.iterator();
while (iterator.hasNext()) {
Entry entry = (Entry) iterator.next();
entry.accept(this);
}
currentdir = saveDir;
}
}
异常类,这个不是重点
public class FileTreatmentException extends RuntimeException {
public FileTreatmentException() {
}
public FileTreatmentException(String message) {
super(message);
}
}
测试类
public class Main {
public static void main(String[] args) {
try {
System.out.println("Making root entries...");
Directory rootDir = new Directory("root");
Directory bindir = new Directory("bin");
Directory tmpdir = new Directory("tmp");
Directory usrdir = new Directory("usr");
rootDir.add(bindir);
rootDir.add(tmpdir);
rootDir.add(usrdir);
bindir.add(new File("vi", 10000));
bindir.add(new File("latex", 20000));
rootDir.accept(new ListVisitor());
} catch (FileTreatmentException e) {
e.printStackTrace();
}
}
}
双重分发
在Visitor模式中,ConcreteElement和ConcreteVisitor这两个角色共同决定了实际进行的处理。这种消息分发的方式一般被称为双重分发。
开闭原则——对扩展开放,对修改关闭
开闭原则(The Open-Closed Principle,OCP),就是在不修改现有代码的前提下进行扩展。比如这里的File和Directory就具有高可复用性,可作为组件复用,如果需要改变对其的处理方式,只需要增加ConcreteVisitor类,而不用修改File和Directory(File的accpet方法不用改变)
优缺点
优点:易于增加ConcreteVisitor角色
缺点:难以增加ConcreteElement角色,如果要增加一个Device类继承了Entry类,那么它肯定也要实现accept方法,那抽象访问者肯定要加一个专门给Device类的访问方法visit(Device device),这样全部的ConcreteVisitor都要实现这个新增方法,非常麻烦。