双重分发通过两次方法调用实现类型驱动的逻辑路由:第一次由元素类accept方法触发访问者visit调用,第二次由JVM根据实参具体类型匹配重载visit方法,彻底规避if-else和instanceof。

双重分发是访问者模式的核心机制,它把“谁操作谁”的决策从运行时类型判断中解耦出来,让类型判定不再靠 if-else 或 switch,也不依赖 instanceof,而是通过两次方法调用(对象分发 + 访问者分发)自动路由到最匹配的处理逻辑。
第一次分发:由被访问对象决定调用哪个访问者方法
每个具体元素类(如 Circle、Rectangle)都实现统一的 accept(Visitor v) 方法,在其中主动调用 v.visit(this)。注意这里的 this 是具体类型(比如 Circle),所以 JVM 能根据实际类型选择 visit(Circle c) 还是 visit(Rectangle r) —— 这是第二次分发的基础。
关键点:
-
元素类不关心自己被怎么处理,只负责“找对人”:
Circle.accept(v)就是说“我是个 Circle,请 Visitor 按 Circle 的方式处理我”; - accept 方法必须在每个子类中重写,哪怕只是调用父类实现,否则无法触发多态分发;
- 如果漏掉某个子类的
accept实现,该类型就无法进入双重分发流程,会退化为静态绑定。
第二次分发:由访问者根据参数类型选择具体 visit 方法
访问者接口定义一组重载的 visit 方法,每个对应一种元素类型:visit(Circle c)、visit(Rectangle r) 等。具体访问者类(如 XMLExporter)实现这些方法。JVM 在运行时根据 visit(...) 调用中实参的**静态类型声明**(即方法签名中的形参类型)和**实际对象类型**,选出最精确匹配的重载版本。
这一步真正实现了“类型判定即分发”:
- 没有 if/else 判定类型,类型信息隐含在方法签名和多态调用链中;
- 新增元素类型时,只需添加新 visit 方法 + 对应 accept 实现,不修改已有访问者逻辑;
- 新增操作(如加一个
PDFExporter)只需实现全部 visit 方法,不碰元素类代码。
为什么能绕过 instanceof?
因为 instanceof 是在运行时检查对象是否属于某类型,而双重分发把类型信息“编码”进了方法调用结构里:第一次分发靠对象的 accept 动态绑定确认“我是谁”,第二次靠 Java 重载解析规则(结合编译期类型与运行期类型)确认“该进哪个 visit”。整个过程由语言机制保障,无需手动判断。
例如:
- 你写
shape.accept(v),shape 是Circle实例 → 调用Circle.accept(v); - 其内部是
v.visit(this),this 类型是Circle→ JVM 匹配到visit(Circle)方法; - 即使
v是XMLExporter,也自动走到XMLExporter.visit(Circle),无需任何类型检查语句。
常见陷阱与应对
双重分发看似优雅,但容易因设计偏差失效:
-
visit 方法参数不能用基类或 Object:若写成
visit(Shape s),所有调用都会落到这个方法,第二次分发就塌了; - 元素类层次要稳定:频繁增删子类会导致访问者接口爆炸式增长(每个新类都要加一个 visit 方法);
- 不适用于需要动态扩展元素类型的场景:此时可考虑用反射 + 注解模拟 visit 分发,或改用策略模式 + 工厂注册。

















