访问者模式在c#中需通过accept+visit(this)实现双分派,accept必须在每个子类重写以保留类型信息,ivisitor接口增减方法会强制编译检查,适用于元素稳定而操作频繁新增的场景。

访问者模式在 C# 中不是“写个接口就完事”,关键在于双分派必须靠 Accept + Visit(this) 配合才能生效;漏掉任一环节,访问逻辑就彻底失效。
为什么 Accept 必须在每个子类里重写
C# 是静态类型语言,方法重载在编译期绑定。如果把 Accept 放在基类里统一写成 visitor.Visit((IElement)this),那只会调用 Visit(IElement),所有子类类型信息全丢光。
-
AddElement的Accept必须显式写成visitor.Visit(this),让编译器看到this是AddElement类型,才能选中Visit(AddElement element) - 不能用
dynamic替代——会绕过编译检查,运行时报错难定位,且性能差 - 子类多时容易漏写
Accept,建议用 Roslyn 源生成器自动生成,或加单元测试覆盖所有子类的Accept调用路径
IVisitor 接口定义不全会直接编译失败
接口里每少一个 Visit(XxxElement) 方法,对应子类的 Accept 就会报错:“没有匹配的重载”。这不是警告,是硬性编译拦截,也是该模式类型安全的核心保障。
- 新增
DivideElement类?必须同步往IVisitor加void Visit(DivideElement element) - 已有所有
ConcreteVisitor实现类(比如Evaluator、Printer)都得补实现,否则编译不过 - 别想着只加默认实现(C# 8+ 接口默认方法)来“偷懒”——访问者逻辑必须由具体类控制,否则失去行为隔离意义
什么时候不该用访问者模式
它只适合「元素结构稳定、操作频繁新增」的场景。一旦元素类本身开始增删,维护成本会指数级上升。
- AST 解析器里
NumberExpression、AddExpression等节点长期固定,但要不断加OptimizeVisitor、ToJsonVisitor、ValidateVisitor——这是典型适用场景 - 如果业务模型层的
User、Order、Product还在快速迭代,硬套 Visitor 会导致每次加新实体都要改三处(接口、所有访问者、所有Accept),得不偿失 - 简单遍历+条件判断(比如
if (x is NumberElement) ... else if (x is AddElement) ...)比 Visitor 更轻量,别为设计而设计
ObjectStructure 不是必须角色,但建议封装
很多教程把 ObjectStructure 当成标配,其实它只是个便利包装:把一堆 IElement 聚合成集合,并统一调用 Accept。你完全可以直接遍历 List<ielement></ielement> 并逐个调用 .Accept(visitor)。
- 需要支持嵌套结构(比如表达式树)?
ObjectStructure可以递归调用子节点的Accept,这时它才有存在价值 - 只是平铺一组对象?跳过它,用
foreach (var e in elements) e.Accept(visitor)更直白 - 如果后续要加过滤、排序、批量状态管理,再把逻辑抽到
ObjectStructure里也不迟
最常被忽略的一点:Visitor 的「新操作」必须真正独立于元素内部状态。如果 Visit(AddElement) 里反复调用 element.Left.Evaluate() 这类本该属于元素自身的方法,说明职责已经混淆——访问者不该越界驱动元素行为,而应专注横向逻辑(如日志、序列化、校验)。










