https://en.wikipedia.org/wiki/Visitor_pattern#Java_example
访问者模式是一种将算法「algorithm」与对象结构 「object structure」分离的软件设计模式。由于这种分离,可以在不修改结构的情况下将新的操作「opertations」添加到现有的对象结构中。它是面向对象编程和软件工程中遵循开放/封闭原则的一种方法。
本质上,访问者允许向类族「a family of classes」添加新的虚拟函数「 virtual functions」,而无需修改类。相反,创建了一个访问者类,该类实现了虚拟函数的所有特定行为「specializations」。访问者将实例引用作为输入,并通过双重分派(double dispatch)实现目标。
虚拟函数(Virtual Function):在面向对象编程中,虚拟函数是指可以在子类中重写的函数,允许在运行时根据对象的实际类型调用相应的函数实现。
类族(Family of Classes):一组具有继承关系或共同接口的类。
访问者类(Visitor Class):一个独立的类,负责实现针对不同类族的具体操作。它通过访问类族中的对象来执行特定行为。
双重分派(Double Dispatch):一种机制,通过两次方法调用来确定运行时行为。第一次分派确定调用哪个访问者方法,第二次分派确定访问者方法中处理的具体对象类型。
(如 Haskell、Rust、Scala、OCaml 等)具有 和类型「sum types 」和模式匹配「pattern matching」的编程语言消除了「obviate」访问者模式的许多优点,因为访问者类能够很容易地在对象的类型上分支,并且如果定义了访问者尚未处理的新对象类型,则会生成编译器错误。
和类型(Sum Types) :和类型是一种复合类型,表示一个值可以是多种类型之一。例如,在函数式编程语言中,
Option
类型可以是Some(value)
或None
。模式匹配(Pattern Matching):一种语言特性,允许根据值的结构或类型进行分支处理。它通常用于处理和类型,能够简洁地匹配不同的情况并执行相应的逻辑。
访问者设计模式可以解决哪些问题?
应该可以在不改变类的情况下为对象结构的(某些)类定义新的操作。
当经常需要新的操作并且对象结构由许多不相关的类组成时,每次需要新的操作时添加新的子类是不灵活的,因为"[...]将所有这些操作分布在不同的节点类中会导致系统难以理解、维护和更改。"
访问者设计模式描述了什么解决方案?
定义一个单独的(访问者)对象,该对象实现要在对象结构的元素上执行的操作。
客户端遍历对象结构并在元素上调用调度操作「 dispatching operation」accept (visitor),该操作将请求"dispatches"(delegates)给"accepted visitor object"。然后,访问者对象对元素执行操作("访问元素")。
这使得可以通过添加新的访问者对象来独立于对象结构的类创建新的操作。
请参见下面的UML类和序列图。
UML class and sequence diagram

在上面的UML类图中,ElementA类并不直接实现新的操作。相反,ElementA实现了一个调度操作「dispatching operation 」accept(visitor),该操作将请求"dispatches" (delegates) 给 "accepted visitor object" (visitor.visitElementA(this)
)。Visitor1类实现了操作(visitElementA(e:ElementA)
).。
然后,ElementB通过分派给visitor.visitElementB(this) 来实现accept(visitor)。Visitor1类实现了操作(visitElementB(e:ElementB)
).。
UML序列图显示了运行时交互:Client对象遍历对象结构的元素(ElementA、ElementB),并对每个元素调用accept(visitor)。
首先,客户端调用ElementA上的accept(visitor), ElementA在 accepted visitor 对象上调用visitElementA(this)。元素本身(this)被传递给 visitor,这样它就可以"visit"ElementA(调用operationA())。
此后,客户端在ElementB上调用accept(visitor),它在"访问"ElementB的访问者(调用operationB())上调用visitElementB(this)。
根据上面这段话,DeepSeek 直接生成代码如下:
java
// Visitor interface
interface Visitor {
void visitElementA(ElementA elementA);
void visitElementB(ElementB elementB);
}
// Concrete Visitor
class Visitor1 implements Visitor {
@Override
public void visitElementA(ElementA elementA) {
System.out.println("Visitor1 is visiting ElementA");
elementA.operationA();
}
@Override
public void visitElementB(ElementB elementB) {
System.out.println("Visitor1 is visiting ElementB");
elementB.operationB();
}
}
// Element interface
interface Element {
void accept(Visitor visitor);
}
// Concrete ElementA
class ElementA implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElementA(this);
}
public void operationA() {
System.out.println("ElementA's operationA is called.");
}
}
// Concrete ElementB
class ElementB implements Element {
@Override
public void accept(Visitor visitor) {
visitor.visitElementB(this);
}
public void operationB() {
System.out.println("ElementB's operationB is called.");
}
}
// Client
public class Client {
public static void main(String[] args) {
// Create elements
ElementA elementA = new ElementA();
ElementB elementB = new ElementB();
// Create visitor
Visitor visitor = new Visitor1();
// Traverse elements and call accept
elementA.accept(visitor);
elementB.accept(visitor);
}
}
python 实现上面的例子(只不过 self对象没用到)
假设有一个类族,包含两种类型的对象:ElementA
和 ElementB
。我们希望为这些对象添加一个新的操作(例如 visit()
),而不修改 ElementA
和 ElementB
的代码。
-
定义类
pythonclass Element: def accept(self, visitor): pass class ElementA(Element): def accept(self, visitor): visitor.visit_element_a(self) class ElementB(Element): def accept(self, visitor): visitor.visit_element_b(self)
-
定义访问者接口:
pythonclass Visitor: def visit_element_a(self, element): pass def visit_element_b(self, element): pass
-
实现具体访问者:
pythonclass ConcreteVisitor(Visitor): def visit_element_a(self, element): print("Visiting ElementA") def visit_element_b(self, element): print("Visiting ElementB")
-
客户端使用:
pythonelements = [ElementA(), ElementB()] visitor = ConcreteVisitor() for element in elements: element.accept(visitor)
输出
pythonVisiting ElementA Visiting ElementB
访问者模式的核心思想
-
不修改类族:访问者模式允许在不修改现有类的情况下,为类族添加新的操作。
-
分离关注点:将数据结构(类族)和操作(访问者)分离,使得操作可以独立变化。
-
双重分派:通过双重分派机制,访问者模式能够在运行时确定具体调用哪个方法。
双重分派的作用
在上面的例子中:
-
第一次分派:
element.accept(visitor)
根据element
的类型(ElementA
或ElementB
)调用对应的accept
方法。 -
第二次分派:
visitor.visit_element_a(self)
或visitor.visit_element_b(self)
根据visitor
的具体类型调用对应的访问者方法。
通过双重分派,访问者模式能够在运行时动态确定具体的行为。
Java实现的另一个例子
以下示例使用Java语言,显示了如何打印节点树的内容(在本例中描述了汽车的组件)。一个访问者类(CarElementPrintVisitor)执行所需的打印操作,而不是为每个节点子类(Wheel、Engine、Body和Car)创建打印方法。因为不同的节点子类需要稍微不同的操作才能正确打印,所以CarElementPrintVisitor会根据传递给其访问方法的参数的类来分派操作。CarElementDoVisitor类似于另一种文件格式的保存操作,也有同样的作用。

java
import java.util.List;
interface CarElement {
void accept(CarElementVisitor visitor);
}
interface CarElementVisitor {
void visit(Body body);
void visit(Car car);
void visit(Engine engine);
void visit(Wheel wheel);
}
class Wheel implements CarElement {
private final String name;
public Wheel(final String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public void accept(CarElementVisitor visitor) {
/*
* accept(CarElementVisitor) in Wheel implements
* accept(CarElementVisitor) in CarElement, so the call
* to accept is bound at run time. This can be considered
* the *first* dispatch. However, the decision to call
* visit(Wheel) (as opposed to visit(Engine) etc.) can be
* made during compile time since 'this' is known at compile
* time to be a Wheel. Moreover, each implementation of
* CarElementVisitor implements the visit(Wheel), which is
* another decision that is made at run time. This can be
* considered the *second* dispatch.
*/
visitor.visit(this);
}
}
class Body implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Engine implements CarElement {
@Override
public void accept(CarElementVisitor visitor) {
visitor.visit(this);
}
}
class Car implements CarElement {
private final List<CarElement> elements;
public Car() {
this.elements = List.of(
new Wheel("front left"), new Wheel("front right"),
new Wheel("back left"), new Wheel("back right"),
new Body(), new Engine()
);
}
@Override
public void accept(CarElementVisitor visitor) {
for (CarElement element : elements) {
element.accept(visitor);
}
visitor.visit(this);
}
}
class CarElementDoVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Moving my body");
}
@Override
public void visit(Car car) {
System.out.println("Starting my car");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Kicking my " + wheel.getName() + " wheel");
}
@Override
public void visit(Engine engine) {
System.out.println("Starting my engine");
}
}
class CarElementPrintVisitor implements CarElementVisitor {
@Override
public void visit(Body body) {
System.out.println("Visiting body");
}
@Override
public void visit(Car car) {
System.out.println("Visiting car");
}
@Override
public void visit(Engine engine) {
System.out.println("Visiting engine");
}
@Override
public void visit(Wheel wheel) {
System.out.println("Visiting " + wheel.getName() + " wheel");
}
}
public class VisitorDemo {
public static void main(final String[] args) {
Car car = new Car();
car.accept(new CarElementPrintVisitor());
car.accept(new CarElementDoVisitor());
}
}
java
Visiting front left wheel
Visiting front right wheel
Visiting back left wheel
Visiting back right wheel
Visiting body
Visiting engine
Visiting car
Kicking my front left wheel
Kicking my front right wheel
Kicking my back left wheel
Kicking my back right wheel
Moving my body
Starting my engine
Starting my car