设计模式-访问者模式
前言
在 Java 开发中,设计模式是常用的一种编码方式。合理使用设计模式,可以帮助开发人员更快地写出可靠、容易维护的代码。本期继续更新设计模式系列,总共 23 种设计模式会用一篇一篇文章来讲解,相关代码已经开源至 Gitee点击跳转 上一篇《Java设计模式(21)模板方法模式》。本文是这个系列的第二十二篇,也是最后一章,继续介绍行为型设计模式中的访问者模式。
访问者模式
访问者模式是一种行为型设计模式。它的核心思想是:把作用于某个对象结构中各元素的操作封装到访问者对象中,让我们可以在不修改元素类的前提下,为这些元素新增操作。
简单说就是:
- 元素类负责接收访问者。
- 访问者类负责处理具体逻辑。
- 对象结构负责遍历元素,并把访问者传进去。
这样一来,Circle、Rectangle 这些元素不用知道“计算面积”或者“绘制图形”到底怎么做,它们只要把自己交给访问者就行。
适用场景
访问者模式适合用在下面这类场景:
- 第一,对象结构比较稳定,不会频繁增加新的元素类型。
- 第二,针对这些对象的操作经常变化,或者后续还会继续增加。
- 第三,不希望把很多不相关的操作都塞进元素类里。
比如:
编译器里的抽象语法树,不同访问者可以做语法检查、代码生成、格式化。
报表系统里的数据节点,不同访问者可以做统计、导出、校验。
图形编辑器里的图形对象,不同访问者可以做绘制、面积计算、序列化。
这篇文章用的是我项目里的图形例子。元素是 Shape,具体元素是 Circle 和 Rectangle,访问者有 AreaCalculator 和 ShapeDrawer。
代码实现
先定义元素接口 Shape。
/**
* 元素接口
* @author Jensen
* @date 2026-01-18
* */
public interface Shape {
void accept(Visitor visitor);
}Shape 里只有一个 accept 方法,它的作用是接收访问者。
这里先不用急着理解为什么要这么写。可以先把它看成一句话:我是一个图形对象,我允许某个访问者来访问我。
再定义访问者接口 Visitor
/**
* 访问者接口
* @author Jensen
* @date 2026-01-18
* */
public interface Visitor {
void visit(Circle circle);
void visit(Rectangle rectangle);
}Visitor 里针对不同图形提供了不同的 visit 方法。
也就是说,访问者知道怎么处理 Circle,也知道怎么处理 Rectangle。以后如果新增一个“计算周长”的访问者,它也可以分别实现 visit(Circle circle) 和 visit(Rectangle rectangle),把逻辑放到访问者类里,接着看圆形 Circle
public class Circle implements Shape {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
public double getRadius() {
return radius;
}
public void accept(Visitor visitor) {
visitor.visit(this);
}
}Circle 保存自己的半径 radius。
accept 方法里调用 visitor.visit(this),这句代码是访问者模式里很关键的一步。Circle 把自己传给访问者,访问者就能拿到 Circle 的数据,然后执行对应逻辑,再看矩形 Rectangle。
public class Rectangle implements Shape {
private double x;
private double y;
public Rectangle(double x, double y) {
this.x = x;
this.y = y;
}
public double getX() {
return x;
}
public double getY() {
return y;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
}Rectangle 和 Circle 的结构很像,只是它保存的是 x 和 y。
到这里,元素部分已经完成了。Circle 和 Rectangle 都没有写“计算面积”或者“绘制图形”的业务逻辑,它们只负责保存自己的数据,并接收访问者,然后看第一个访问者:AreaCalculator
public class AreaCalculator implements Visitor {
@Override
public void visit(Circle circle) {
System.out.println("Area of Circle: " + circle.getRadius() * circle.getRadius());
}
@Override
public void visit(Rectangle rectangle) {
System.out.println("Area of Rectangle: " + rectangle.getX() * rectangle.getY());
AreaCalculator 专门负责面积计算。
访问到 Circle 时,它拿半径做计算。访问到 Rectangle 时,它拿 x 和 y 做计算。
这里需要注意一点,当前项目里的圆面积计算写的是 radius * radius,所以运行结果是 100.0。严格数学公式应该是 πr²,不过这篇文章按项目代码本身来讲,不额外改动实现,再看第二个访问者:ShapeDrawer
public class ShapeDrawer implements Visitor {
@Override
public void visit(Circle circle) {
System.out.println("Drawing circle: " + circle.getRadius());
}
@Override
public void visit(Rectangle rectangle) {
System.out.println("Drawing rectangle X is: " + rectangle.getX() + "and Y is: " + rectangle.getY());
}
}ShapeDrawer 专门负责绘制逻辑。
这个类和 AreaCalculator 访问的是同一批图形对象,但做的事情完全不一样。这就是访问者模式比较舒服的地方:新增一个操作时,只需要新增一个访问者类,不需要把 Circle、Rectangle 再翻出来改一遍,最后看对象结构 ShapeCollection。
public class ShapeCollection {
private final List<Shape> shapes = new ArrayList<>();
public void addShape(Shape shape) {
shapes.add(shape);
}
public void accept(Visitor visitor) {
for (Shape shape : shapes) {
shape.accept(visitor);
}
}
}ShapeCollection 保存一组 Shape。
它的 accept 方法会遍历所有图形,然后把访问者传给每个图形。也就是说,外部只要把访问者交给 ShapeCollection,它就会让这个访问者依次访问集合里的所有元素。
测试代码
在 Main 方法中,访问者模式的测试代码如下:
System.out.println("------------------------------访问者模式-----------------------------");
ShapeCollection shapeCollection = new ShapeCollection();
shapeCollection.addShape(new com.nn.visitor.impl.shape.Circle(10));
shapeCollection.addShape(new Rectangle(5,6));
shapeCollection.accept(new AreaCalculator());
shapeCollection.accept(new ShapeDrawer())这里先创建 ShapeCollection,然后放入一个半径为 10 的圆形,再放入一个 5 和 6 的矩形。
接着执行两次 accept:
- 第一次传入 AreaCalculator,表示对集合里的所有图形计算面积。
- 第二次传入 ShapeDrawer,表示对集合里的所有图形执行绘制操作。
运行结果
------------------------------访问者模式-----------------------------
Area of Circle: 100.0
Area of Rectangle: 30.0
Drawing circle: 10.0
Drawing rectangle X is: 5.0and Y is: 6.0从输出可以看出来,同一批图形对象被访问了两次。
第一次访问时,执行的是面积计算逻辑。
第二次访问时,执行的是绘制逻辑。
Circle 和 Rectangle 并没有关心外部到底要做什么,它们只是在 accept 方法里把自己传给访问者。真正的业务行为都在访问者类中。
总结
访问者模式适合处理对象结构稳定、操作经常变化的场景。
在这篇例子里,Circle 和 Rectangle 作为图形元素,只负责保存自己的数据和接收访问者。AreaCalculator、ShapeDrawer 作为访问者,分别负责面积计算和图形绘制。ShapeCollection 则负责管理图形集合,并让访问者依次访问这些图形。这个模式的好处是新增操作方便,缺点是新增元素类型成本比较高。所以使用访问者模式前,可以先问一句:这个系统以后更可能新增操作,还是更可能新增元素类型?如果经常新增操作,访问者模式会比较合适。如果经常新增元素类型,就要谨慎一点。