Skip to content

访问者模式

详细介绍

访问者模式是一种行为设计模式,它允许你在不改变类结构的情况下为类的对象增加新的操作。通过使用访问者模式,可以将数据结构和数据操作分离开来。访问者模式的核心思想是将操作逻辑从对象结构中抽离出来,封装到一个访问者类中,使得新的操作可以通过新增访问者类的方式实现,而无需修改对象结构本身。

类图

访问者模式

主要角色

访问者模式主要包括以下几个角色:

  1. 抽象访问者(Visitor):定义一个接口,声明对每一个具体元素访问的方法。
  2. 具体访问者(ConcreteVisitor):实现 Visitor 接口,为每一种具体元素实现相应的操作。
  3. 抽象元素(Element):定义一个接受访问者的方法(accept 方法),这个方法通常接受一个访问者对象作为参数。
  4. 具体元素(ConcreteElement):实现 Element 接口,定义接受访问者的方法,通常会调用访问者对象针对该元素的方法。
  5. 对象结构(ObjectStructure):包含元素集合,可以迭代这些元素并接受访问者对其进行访问。

优缺点

优点

  • 符合单一职责原则:将对象结构与操作解耦。
  • 符合开闭原则:可以在不修改元素类的情况下增加新的操作,只需新增访问者类即可。
  • 灵活性高:通过访问者模式,可以很容易地增加新的操作。

缺点

  • 增加复杂性:如果对象结构频繁变化,可能需要频繁修改访问者类,导致复杂性增加。
  • 破坏封装:访问者模式需要对象结构对访问者公开其内部细节,可能破坏封装性。

使用/适用场景

  • 对象结构稳定,但经常需要对其进行不同操作的场景:如编译器的语法树遍历、各种报表生成等。
  • 需要对一个复杂对象结构进行许多不同的操作:如电商系统中的订单处理、对商品和订单的不同统计等。
  • 对象结构中各元素类比较少:因为增加新的元素类需要修改所有现有的访问者类。

示例

java
// 访问者接口
interface Visitor {
    void visit(Circle circle);
    void visit(Rectangle rectangle);
}

// 具体访问者:面积计算
class AreaVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        double area = Math.PI * circle.getRadius() * circle.getRadius();
        System.out.println("Circle area: " + area);
    }

    @Override
    public void visit(Rectangle rectangle) {
        double area = rectangle.getWidth() * rectangle.getHeight();
        System.out.println("Rectangle area: " + area);
    }
}

// 具体访问者:绘制形状
class DrawVisitor implements Visitor {
    @Override
    public void visit(Circle circle) {
        System.out.println("Drawing a circle with radius: " + circle.getRadius());
    }

    @Override
    public void visit(Rectangle rectangle) {
        System.out.println("Drawing a rectangle with width: " + rectangle.getWidth() + " and height: " + rectangle.getHeight());
    }
}

// 元素接口
interface Element {
    void accept(Visitor visitor);
}

// 具体元素:圆形
class Circle implements Element {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    public double getRadius() {
        return radius;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 具体元素:矩形
class Rectangle implements Element {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    public double getWidth() {
        return width;
    }

    public double getHeight() {
        return height;
    }

    @Override
    public void accept(Visitor visitor) {
        visitor.visit(this);
    }
}

// 对象结构
class ShapeCollection {
    private List<Element> shapes = new ArrayList<>();

    public void addShape(Element shape) {
        shapes.add(shape);
    }

    public void accept(Visitor visitor) {
        for (Element shape : shapes) {
            shape.accept(visitor);
        }
    }
}

// 测试访问者模式
public class VisitorPatternDemo {
    public static void main(String[] args) {
        ShapeCollection shapeCollection = new ShapeCollection();
        shapeCollection.addShape(new Circle(5));
        shapeCollection.addShape(new Rectangle(3, 4));

        AreaVisitor areaVisitor = new AreaVisitor();
        DrawVisitor drawVisitor = new DrawVisitor();

        System.out.println("Calculating area:");
        shapeCollection.accept(areaVisitor);

        System.out.println("\nDrawing shapes:");
        shapeCollection.accept(drawVisitor);
    }
}

双分派技术

双分派(Double Dispatch)是一种技术,使得选择执行的操作不仅依赖于一个对象的运行时类型,还依赖于另一个对象的运行时类型。访问者模式利用双分派技术来实现操作的选择,即通过元素对象的 accept 方法将访问者传递给元素,并在访问者中根据传入元素的具体类型选择相应的操作。

示例解释

在访问者模式中,双分派体现为:

  1. 第一步分派:元素对象调用访问者的 visit 方法,并将自己(this)作为参数传递给访问者。
  2. 第二步分派:访问者对象根据传入的元素类型选择相应的 visit 方法执行。

例如,在 Circleaccept 方法中:

java
@Override
public void accept(Visitor visitor) {
    visitor.visit(this);  // 第一步分派
}

访问者的 visit 方法中:

java
java复制代码@Override
public void visit(Circle circle) {
    // 具体操作逻辑
}

通过这种方式,实现了根据运行时类型选择具体操作的双分派。

总结

访问者模式通过将操作与对象结构分离,增强了系统的灵活性和可扩展性。尽管增加了系统的复杂性,但在处理稳定对象结构且需要扩展操作的场景中,访问者模式是一个非常有用的模式。双分派技术在访问者模式中发挥了关键作用,使得系统可以根据对象的运行时类型选择具体的操作逻辑

Released under the MIT License.