访问者设计模式是一种行为模式,允许您向现有对象结构添加新作,而无需修改其类。
它通过允许您将算法与其作的对象分开来实现这一点。
它在以下情况下特别有用:
您有一个复杂的对象结构(如 AST、文档或 UI 元素),您希望对其执行多个不相关的作。
您希望 在不更改其源代码的情况下向类添加新行为。
您需要根据对象的具体类型执行不同的作,而不是诉诸长链 或 检查。if-elseinstanceof
通过访问者模式, 您可以将作外部化为单独访问者客类。每个访问者都会为每种元素类型实现行为,而元素只是接受访问者。这可以保持数据结构的整洁,逻辑模块化和可扩展性。
让我们通过一个真实世界的示例来了解如何应用访问者模式来将行为与结构清晰地分离,并使我们的系统更容易扩展,而无需触及现有类。
问题:向形状层次结构添加作
想象一下,您正在构建一个 支持多种形状类型的矢量图形编辑器:
Circle
Rectangle
Triangle
每个形状都是公共层次结构的一部分,必须支持各种作,例如:
在屏幕上渲染
计算面积
导出为 SVG
序列化为 JSON
最简单的方法是将所有这些方法添加到每个形状类:
public interface Shape {void accept(ShapeVisitor visitor);
}
public class Circle implements Shape {private final double radius;public Circle(double radius) {this.radius = radius;}public double getRadius() {return radius;}@Overridepublic void accept(ShapeVisitor visitor) {visitor.visitCircle(this);}
}
为什么这会崩溃
此解决方案对于几个作似乎很好,但随着添加新的作或形状类型,很快就会出现问题。
- 1. 违反单一责任原则
每个形状类现在包含多个不相关的职责:
几何计算
绘图
序列化
导出格式
可能的印刷、造型等。
这会使班级膨胀并使其更难维护。 - 2. 难以扩展
如果您需要添加新作(例如 ),则必须:generatePdf()
修改层次结构中的每个类
重新编译所有内容
可能会破坏现有逻辑
这违反了开放/封闭原则——类应该开放以进行扩展,但关闭以进行修改。 - 3. 你并不总是控制班级
如果形状类是第三方库或生成的代码的一部分,该怎么办?您无法轻松直接添加新行为。
我们真正需要什么
我们需要一个解决方案,让我们能够:
将作与形状类分开
添加新行为而不修改现有类
避免重复检查或使用类型开关来处理不同的形状instanceof
这正是访问者设计模式旨在解决的问题。
访问者模式
通过访问者设计模式, 您可以将算法与其作的对象分开。它使您能够将新作添加到类层次结构中,而无需修改类本身。
当您有以下情况时,这尤其有用:
一组稳定的元素类(例如形状)
需要 跨这些类工作的一组作(例如,渲染、导出、计算)
类图
- 1. 元素接口(例如Shape)
表示对象结构中的对象(例如图形形状、文档节点、AST 元素)。
声明单个方法:
void accept(Visitor visitor);
每个想要访问的类都必须实现此接口。
这允许访问者被“接受”到对象中,以便它可以对其执行作。
- 2. 具体元素(例如, CircleRectangle)
实现 接口。Element
在方法中 ,他们使用 调用访问者的相应方法 。accept()visitor.visitX(this) - 3. 访问者界面
声明一组 方法 — 每个具体元素类型一个。visit()
每种方法都是为处理特定类型的元素而定制的。
此接口允许您定义应用于模型中各种元素的外部作。 - 4. 混凝土访问者(例如AreaCalculatorVisitor)
实现 接口。Visitor
每个访问者都代表需要 跨元素执行的特定作。
实现应用于元素的特定行为(例如,导出、验证、转换)
实现访问者模式
让我们使用 Visitor Pattern 重构具有多个形状 (, ) 的图形系统来执行两个作:CircleRectangle
计算 每个形状的面积
将形状导出为 SVG 格式
- 1. 定义 接口(元素)Shape
所有形状都必须接受访问者。
public interface Shape {void accept(ShapeVisitor visitor);
}
- 2. 创建具体形状类(元素)
每个形状类实现 并委托给访问者。accept()
🔵 圈
public class Circle implements Shape {private final double radius;public Circle(double radius) {this.radius = radius;}public double getRadius() {return radius;}@Overridepublic void accept(ShapeVisitor visitor) {visitor.visitCircle(this);}
}
🟥 矩形
public class Rectangle implements Shape {private final double width;private final double height;public Rectangle(double width, double height) {this.width = width;this.height = height;}public double getWidth() {return width;}public double getHeight() {return height;}@Overridepublic void accept(ShapeVisitor visitor) {visitor.visitRectangle(this);}
}
- 3. 定义访问者界面
每种方法对应于一种形状类型。
public interface ShapeVisitor {void visitCircle(Circle circle);void visitRectangle(Rectangle rectangle);
}
- 4. 实施具体访问者
📏 面积计算器访问者
public class AreaCalculatorVisitor implements ShapeVisitor {@Overridepublic void visitCircle(Circle circle) {double area = Math.PI * circle.getRadius() * circle.getRadius();System.out.println("Area of Circle: " + area);}@Overridepublic void visitRectangle(Rectangle rectangle) {double area = rectangle.getWidth() * rectangle.getHeight();System.out.println("Area of Rectangle: " + area);}
}
🖼️ SVG 导出器访问者
public class SvgExporterVisitor implements ShapeVisitor {@Overridepublic void visitCircle(Circle circle) {System.out.println("<circle r=\"" + circle.getRadius() + "\" />");}@Overridepublic void visitRectangle(Rectangle rectangle) {System.out.println("<rect width=\"" + rectangle.getWidth() +"\" height=\"" + rectangle.getHeight() + "\" />");}
}
- 5. 客户端代码
现在,您可以使用任何访问器对形状结构进行作。
public class VisitorPatternDemo {public static void main(String[] args) {List<Shape> shapes = List.of(new Circle(5),new Rectangle(10, 4),new Circle(2.5));System.out.println("=== Calculating Areas ===");ShapeVisitor areaCalculator = new AreaCalculatorVisitor();for (Shape shape : shapes) {shape.accept(areaCalculator);}System.out.println("\n=== Exporting to SVG ===");ShapeVisitor svgExporter = new SvgExporterVisitor();for (Shape shape : shapes) {shape.accept(svgExporter);}}
}
输出
=== Calculating Areas ===
Area of Circle: 78.53981633974483
Area of Rectangle: 40.0
Area of Circle: 19.634954084936208=== Exporting to SVG ===
<circle r="5.0" />
<rect width="10.0" height="4.0" />
<circle r="2.5" />
我们取得了什么成就
解耦逻辑:形状类是干净的;逻辑存在于访问者中
开放/关闭原则:轻松添加新访问者(例如,无需接触形状)JsonExporterVisitor
双重调度:无需进行类型检查或进行类型检查instanceof
可重用性和可维护性:每个访问者专注于一项作,并且可以单独测试