访问者模式通过分离变量结构与计算逻辑实现扩展性,变量类仅含Accept方法,新增统计、校验等功能只需添加对应访问者实现,无需修改原有类型;所有访问者统一实现ExprVisitor接口,通过双分派和显式类型switch保证类型安全与可维护性。

访问者模式在变量计算逻辑扩展中,核心是把“怎么算”从“变量本身”里抽出来。变量结构(比如不同类型的数值、表达式节点)保持不变,新增统计、校验、序列化、导出等计算行为,只需加新访问者,不碰原有变量类。
明确变量结构与计算职责分离
变量不是简单 float 或 int,而是有语义的结构,例如:
-
Constant:固定值,如
42、"hello" -
VariableRef:引用名,如
x、user.balance -
BinaryOp:二元运算,如
a + b、price * qty -
FunctionCall:函数调用,如
max(a, b)、round(x, 2)
这些类型共同实现一个统一接口(如 Expr),只声明 Accept(visitor) 方法,不包含任何具体计算逻辑。
定义通用访问者接口并按需实现
所有计算行为都通过实现 ExprVisitor 接口完成,每个方法对应一种变量结构:
type ExprVisitor interface {
VisitConstant(*Constant)
VisitVariableRef(*VariableRef)
VisitBinaryOp(*BinaryOp)
VisitFunctionCall(*FunctionCall)
}例如新增一个「变量依赖分析」功能,只需写:
- DependencyAnalyzer:记录哪些变量名被读取,用于编译期检查或热重载追踪
- EvalVisitor:带上下文环境执行求值,支持运行时变量注入
-
SQLRenderVisitor:把表达式转成参数化 SQL 片段,如
age > ? AND status = ? - DotGraphVisitor:生成可视化 AST 图,便于调试复杂公式
每种访问者互不影响,也不需要修改 BinaryOp 或 FunctionCall 的源码。
关键细节:双分派与类型安全处理
Go 中没有方法重载,所以访问者内部需用类型断言或反射识别具体类型。推荐显式 switch:
func (v *EvalVisitor) Visit(expr Expr) {
switch e := expr.(type) {
case *Constant:
v.VisitConstant(e)
case *VariableRef:
v.VisitVariableRef(e)
case *BinaryOp:
v.VisitBinaryOp(e)
case *FunctionCall:
v.VisitFunctionCall(e)
}
}这样既保持类型安全,又避免运行时 panic。若未来增加 ArrayIndex 类型,只需在所有访问者中补一个 VisitArrayIndex 方法和 switch 分支,变量结构本身完全不动。
配合对象结构统一调度
实际业务中变量常以集合形式出现,比如规则引擎里的条件列表、报表配置中的指标表达式组。这时引入 ExprList 或 RuleSet 作为对象结构:
- 它持有多个
Expr实例 - 提供
AcceptAll(visitor)方法,遍历并调用每个表达式的Accept - 支持条件过滤,例如只对标记为
isCritical=true的表达式执行校验访问者
这种封装让上层调用干净利落:ruleSet.AcceptAll(&SQLRenderVisitor{}),一行代码完成整套转换。

















