Skip to main content

访问者模式 (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());

有何问题​

  1. 元素类职责过重,操作扩展不灵活。
  2. 新增操作需修改所有元素类,违反开闭原则。
  3. 代码难以维护和扩展。

解决方案​

访问者模式的定义​

表示一个作用于某对象结构中的各元素的操作,使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。

应用访问者模式的思路​

  • 定义访问者接口,每个操作对应一个方法。
  • 元素类提供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());
}
}

访问者重写示例​

示例结构​


示例代码​

见上文"访问者模式的实现"部分。


模式讲解​

访问者模式的功能​

  • 新增操作无需修改元素类,扩展性强。
  • 适用于对象结构稳定、操作多变的场景。
  • 分离数据结构与操作。

实现成接口​

  • 访问者和元素通常定义为接口或抽象类。

使用工厂方法​

  • 可结合工厂方法模式,统一创建访问者对象。

切换产品簇​

  • 可用于切换不同的访问者实现,如不同统计、打印等。

执行时序图​