访问者模式 (Visitor)
场景问题
问题案例
考虑一个对象结构(如文件系统、抽象语法树),需要对其中的元素进行多种操作(如统计大小、打印结构、查找等),但不希望在元素类中添加这些操作。
不用模式的解决方案
实现简化版本
最直接的做法是在元素类中添加所有操作方法:
public class File {
private String name;
private int size;
public File(String name, int size) { this.name = name; this.size = size; }
public int getSize() { return size; }
public void print() { System.out.println("文件:" + name); }
}
public class Directory {
private String name;
private List<File> files = new ArrayList<>();
public Directory(String name) { this.name = name; }
public void addFile(File file) { files.add(file); }
public int getSize() { return files.stream().mapToInt(File::getSize).sum(); }
public void print() {
System.out.println("目录:" + name);
for (File f : files) f.print();
}
}
使用示例
Directory dir = new Directory("docs");
dir.addFile(new File("a.txt", 10));
dir.addFile(new File("b.txt", 20));
dir.print();
System.out.println(dir.getSize());
有何问题
- 元素类职责过重,操作扩展不灵活。
- 新增操作需修改所有元素类,违反开闭原则。
- 代码难以维护和扩展。
解决方案
访问者模式的定义
表示一个作用于某对象结构中的各元素的操作,使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。
应用访问者模式的思路
- 定义访问者接口,每个操作对应一个方法。
- 元素类提供
accept方法,接收访问者。 - 新增操作只需新增访问者类,无需修改元素类。
访问者模式的结构
- Visitor:抽象访问者,定义对元素的操作方法。
- ConcreteVisitor:具体访问者,实现具体操作。
- Element:抽象元素,定义
accept方法。 - ConcreteElementA/B:具体元素,实现
accept方法。
访问者模式的实现
// 抽象访问者
public interface Visitor {
void visit(FileElement file);
void visit(DirectoryElement dir);
}
// 抽象元素
public interface Element {
void accept(Visitor visitor);
}
// 具体元素:文件
public class FileElement implements Element {
private String name;
private int size;
public FileElement(String name, int size) { this.name = name; this.size = size; }
public int getSize() { return size; }
public void accept(Visitor visitor) { visitor.visit(this); }
public String getName() { return name; }
}
// 具体元素:目录
public class DirectoryElement implements Element {
private String name;
private List<Element> children = new ArrayList<>();
public DirectoryElement(String name) { this.name = name; }
public void add(Element e) { children.add(e); }
public List<Element> getChildren() { return children; }
public void accept(Visitor visitor) { visitor.visit(this); }
public String getName() { return name; }
}
// 具体访问者:统计大小
public class SizeVisitor implements Visitor {
private int totalSize = 0;
public void visit(FileElement file) { totalSize += file.getSize(); }
public void visit(DirectoryElement dir) {
for (Element e : dir.getChildren()) {
e.accept(this);
}
}
public int getTotalSize() { return totalSize; }
}
// 具体访问者:打印结构
public class PrintVisitor implements Visitor {
private int depth = 0;
public void visit(FileElement file) {
System.out.println(" ".repeat(depth) + "文件:" + file.getName());
}
public void visit(DirectoryElement dir) {
System.out.println(" ".repeat(depth) + "目录:" + dir.getName());
depth++;
for (Element e : dir.getChildren()) {
e.accept(this);
}
depth--;
}
}
使用示例
public class Client {
public static void main(String[] args) {
DirectoryElement root = new DirectoryElement("docs");
root.add(new FileElement("a.txt", 10));
root.add(new FileElement("b.txt", 20));
DirectoryElement sub = new DirectoryElement("images");
sub.add(new FileElement("c.png", 30));
root.add(sub);
PrintVisitor printVisitor = new PrintVisitor();
root.accept(printVisitor);
SizeVisitor sizeVisitor = new SizeVisitor();
root.accept(sizeVisitor);
System.out.println("总大小:" + sizeVisitor.getTotalSize());
}
}
访问者重写示例
示例结构
示例代码
见上文"访问者模式的实现"部分。
模式讲解
访问者模式的功能
- 新增操作无需修改元素类,扩展性强。
- 适用于对象结构稳定、操作多变的场景。
- 分离数据结构与操作。
实现成接口
- 访问者和元素通常定义为接口或抽象类。
使用工厂方法
- 可结合工厂方法模式,统一创建访问者对象。
切换产品簇
- 可用于切换不同的访问者实现,如不同统计、打印等。