访问者模式 (Visitor Pattern)
表示一个作用于某对象结构中的各元素的操作
模式定义
访问者模式是一种行为型设计模式,它表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素类的前提下定义作用于这些元素的新操作。访问者模式将数据结构与数据操作分离,使得系统有良好的扩展性,在不修改现有类的情况下增加新的操作。
模式结构
graph TD
A[Visitor] --> B[ConcreteVisitor]
C[Element] --> D[ConcreteElement]
B --> D
实现方式
基础实现
定义访问者接口和元素接口,实现具体访问者和具体元素类。
// 访问者接口
interface Visitor {
void visit(Keyboard keyboard);
void visit(Monitor monitor);
void visit(Mouse mouse);
void visit(Computer computer);
}
// 元素接口
interface ComputerPart {
void accept(Visitor visitor);
}
// 具体元素类
class Keyboard implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Monitor implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Mouse implements ComputerPart {
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}
class Computer implements ComputerPart {
ComputerPart[] parts;
public Computer() {
parts = new ComputerPart[] {new Mouse(), new Keyboard(), new Monitor()};
}
@Override
public void accept(Visitor visitor) {
for (int i = 0; i < parts.length; i++) {
parts[i].accept(visitor);
}
visitor.visit(this);
}
}
// 具体访问者类
class ComputerPartDisplayVisitor implements Visitor {
@Override
public void visit(Keyboard keyboard) {
System.out.println("显示键盘");
}
@Override
public void visit(Monitor monitor) {
System.out.println("显示显示器");
}
@Override
public void visit(Mouse mouse) {
System.out.println("显示鼠标");
}
@Override
public void visit(Computer computer) {
System.out.println("显示计算机");
}
}
使用示例
public class VisitorPatternDemo {
public static void main(String[] args) {
ComputerPart computer = new Computer();
computer.accept(new ComputerPartDisplayVisitor());
}
}
经典案例
1. 购物车系统
购物车系统中计算不同商品价格和折扣是访问者模式的应用:
// 商品元素接口
interface ItemElement {
public int accept(ShoppingCartVisitor visitor);
}
// 具体商品类
class Book implements ItemElement {
private int price;
private String isbnNumber;
public Book(int price, String isbnNumber) {
this.price = price;
this.isbnNumber = isbnNumber;
}
public int getPrice() {
return price;
}
public String getIsbnNumber() {
return isbnNumber;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
class Fruit implements ItemElement {
private int pricePerKg;
private int weight;
private String name;
public Fruit(int pricePerKg, int weight, String name) {
this.pricePerKg = pricePerKg;
this.weight = weight;
this.name = name;
}
public int getPricePerKg() {
return pricePerKg;
}
public int getWeight() {
return weight;
}
public String getName() {
return name;
}
@Override
public int accept(ShoppingCartVisitor visitor) {
return visitor.visit(this);
}
}
// 访问者接口
interface ShoppingCartVisitor {
int visit(Book book);
int visit(Fruit fruit);
}
// 具体访问者类 - 价格计算访问者
class ShoppingCartVisitorImpl implements ShoppingCartVisitor {
@Override
public int visit(Book book) {
int cost = 0;
// 对于超过50元的书给予10元折扣
if (book.getPrice() > 50) {
cost = book.getPrice() - 10;
} else {
cost = book.getPrice();
}
System.out.println("书: ISBN(" + book.getIsbnNumber() + ") 价格: " + cost);
return cost;
}
@Override
public int visit(Fruit fruit) {
int cost = fruit.getPricePerKg() * fruit.getWeight();
System.out.println(fruit.getName() + " 价格: " + cost);
return cost;
}
}
// 使用示例
public class ShoppingCartClient {
public static void main(String[] args) {
ItemElement[] items = new ItemElement[] {
new Book(200, "ISBN123456"),
new Book(100, "ISBN789012"),
new Fruit(10, 2, "苹果"),
new Fruit(5, 5, "橙子")
};
int total = calculatePrice(items);
System.out.println("总价: " + total);
}
private static int calculatePrice(ItemElement[] items) {
ShoppingCartVisitor visitor = new ShoppingCartVisitorImpl();
int sum = 0;
for (ItemElement item : items) {
sum += item.accept(visitor);
}
return sum;
}
}
2. 文件系统操作
文件系统中对不同类型的文件执行操作也是访问者模式的应用:
// 文件系统元素接口
interface FileSystemElement {
public void accept(FileSystemVisitor visitor);
}
// 具体元素类
class File implements FileSystemElement {
private String name;
private int size;
private String type;
public File(String name, int size, String type) {
this.name = name;
this.size = size;
this.type = type;
}
public String getName() {
return name;
}
public int getSize() {
return size;
}
public String getType() {
return type;
}
@Override
public void accept(FileSystemVisitor visitor) {
visitor.visit(this);
}
}
class Directory implements FileSystemElement {
private String name;
private List elements = new ArrayList<>();
public Directory(String name) {
this.name = name;
}
public void addElement(FileSystemElement element) {
elements.add(element);
}
public String getName() {
return name;
}
public List getElements() {
return new ArrayList<>(elements);
}
@Override
public void accept(FileSystemVisitor visitor) {
visitor.visit(this);
// 递归访问所有子元素
for (FileSystemElement element : elements) {
element.accept(visitor);
}
}
}
// 访问者接口
interface FileSystemVisitor {
void visit(File file);
void visit(Directory directory);
}
// 具体访问者类 - 文件统计访问者
class FileStatisticsVisitor implements FileSystemVisitor {
private int totalFiles = 0;
private int totalDirectories = 0;
private int totalSize = 0;
@Override
public void visit(File file) {
totalFiles++;
totalSize += file.getSize();
System.out.println("文件: " + file.getName() + " (" + file.getSize() + " bytes, " + file.getType() + ")");
}
@Override
public void visit(Directory directory) {
totalDirectories++;
System.out.println("目录: " + directory.getName());
}
public void printStatistics() {
System.out.println("\n=== 文件系统统计 ===");
System.out.println("文件总数: " + totalFiles);
System.out.println("目录总数: " + totalDirectories);
System.out.println("总大小: " + totalSize + " bytes");
}
}
// 具体访问者类 - XML导出访问者
class XMLExportVisitor implements FileSystemVisitor {
private StringBuilder xml = new StringBuilder();
private int indentLevel = 0;
@Override
public void visit(File file) {
addIndent();
xml.append(" \n");
}
@Override
public void visit(Directory directory) {
addIndent();
xml.append("\n");
indentLevel++;
}
private void addIndent() {
for (int i = 0; i < indentLevel; i++) {
xml.append(" ");
}
}
public String getXML() {
return xml.toString();
}
}
// 使用示例
public class FileSystemDemo {
public static void main(String[] args) {
// 构建文件系统结构
Directory root = new Directory("root");
Directory documents = new Directory("documents");
Directory images = new Directory("images");
documents.addElement(new File("resume.doc", 1024, "document"));
documents.addElement(new File("reference.pdf", 2048, "pdf"));
images.addElement(new File("photo1.jpg", 204800, "image"));
images.addElement(new File("photo2.jpg", 307200, "image"));
root.addElement(documents);
root.addElement(images);
root.addElement(new File("readme.txt", 512, "text"));
// 使用统计访问者
System.out.println("=== 文件系统统计 ===");
FileStatisticsVisitor statisticsVisitor = new FileStatisticsVisitor();
root.accept(statisticsVisitor);
statisticsVisitor.printStatistics();
// 使用XML导出访问者
System.out.println("\n=== XML导出 ===");
XMLExportVisitor xmlVisitor = new XMLExportVisitor();
root.accept(xmlVisitor);
System.out.println(xmlVisitor.getXML());
}
}
应用场景
- 对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作
- 需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类
- 购物车系统中计算商品总价和折扣
- 编译器设计中对不同类型的节点进行不同操作
- 文件系统中对不同类型的文件执行操作
- XML文档解析中对不同节点类型进行处理
- GUI组件中对不同组件执行操作
优缺点
优点
- 扩展性好:可以在不修改对象结构中的元素的情况下定义新的操作
- 符合单一职责原则:相关的行为集中到一个访问者对象中,便于维护
- 符合开闭原则:增加新的访问者无需修改现有代码
- 灵活性高:可以在运行时动态确定需要执行的操作
缺点
- 违反封装性:访问者模式要求访问者对象访问并调用每一个元素对象的操作,这意味着元素对象有时候必须暴露一些自己的内部状态和内部操作
- 增加复杂性:每增加一个元素类,所有的访问者类都需要增加相应的操作
- 具体元素变更困难:如果元素类经常变动,访问者模式将变得难以维护