Loading...

设计模式-访问者模式

前言

在 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);
    }
}

RectangleCircle 的结构很像,只是它保存的是 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

从输出可以看出来,同一批图形对象被访问了两次。

第一次访问时,执行的是面积计算逻辑。

第二次访问时,执行的是绘制逻辑。

CircleRectangle 并没有关心外部到底要做什么,它们只是在 accept 方法里把自己传给访问者。真正的业务行为都在访问者类中。

总结

访问者模式适合处理对象结构稳定、操作经常变化的场景。

在这篇例子里,CircleRectangle 作为图形元素,只负责保存自己的数据和接收访问者。AreaCalculatorShapeDrawer 作为访问者,分别负责面积计算和图形绘制。ShapeCollection 则负责管理图形集合,并让访问者依次访问这些图形。这个模式的好处是新增操作方便,缺点是新增元素类型成本比较高。所以使用访问者模式前,可以先问一句:这个系统以后更可能新增操作,还是更可能新增元素类型?如果经常新增操作,访问者模式会比较合适。如果经常新增元素类型,就要谨慎一点。

0

回到顶部