go中解释器模式不常用,因缺乏泛型、反射弱,易写成低效的“java模仿体”;推荐手写lexer+parser构建ast,直接实现evaluate方法,避免接口抽象和运行时类型断言。

解释器模式在 Go 里不常用,别硬套
Go 语言没有泛型(旧版本)和反射友好度低,加上解释器模式天然依赖递归下降、AST 遍历和大量接口抽象,用 Go 写容易写成“Java 模仿体”——接口堆砌、类型断言泛滥、性能掉得明显。真实业务中,govaluate、expr 这类库背后是手写 lexer + parser,不是靠 Interpret() 接口组合出来的。
真要手写规则引擎,从 lexer + parser 开始,别从 Interpreter 接口起步
用户真正需要的是“输入字符串,输出 bool/int/float”,不是设计模式教科书。Go 生态里靠谱路径是:
- 用
text/scanner或gocc生成 lexer,识别==、&&、变量名、数字字面量 - 手写递归下降 parser(不用 yacc/bison),把
a > 10 && b == "ok"变成结构体树,比如AndExpr{Left: GtExpr{...}, Right: EqExpr{...}} - 定义一个
Evaluate(ctx map[string]interface{}) (interface{}, error)方法挂在每个 AST 节点上,而不是抽象出Expression接口再实现一堆TerminalExpression - 避免在
Evaluate里做类型转换:提前在 parser 阶段确定字段类型(如StringLiteral.Value string),运行时直接取,不assert
expr 库够用,但要注意它的变量绑定方式
多数人卡在变量传入这步,不是语法写错。用 expr.Eval("user.Age > 18", map[string]interface{}{"user": user}) 时:
-
user必须是 struct 指针或导出字段的 struct,匿名字段不被识别 - 不能传
map[string]interface{}嵌套太深,expr默认只展开一层,想支持req.Header["X-Id"]得自己注册函数GetMapValue(m map[string]interface{}, k string) - 错误信息是
"error: undefined identifier 'xxx'",实际是变量没传进 context,不是语法错 - 性能敏感场景慎用:每次
Eval都重解析,应预编译为program,调用program.Run(map[string]interface{})
自定义函数注入比扩展 AST 更实际
规则引擎总要调外部逻辑,比如验证手机号、查缓存。Go 里最轻量做法是往 expr 或 govaluate 的 context 注入函数值:
立即学习“go语言免费学习笔记(深入)”;
-
"is_mobile(user.Phone)"→ 注册map[string]interface{}{"is_mobile": func(s string) bool { return regexp.MustCompile(`^1[3-9]\d{9}$`).MatchString(s) }} - 函数参数类型必须是
string/int/float64/bool或其指针,expr不支持自定义 struct 参数 - 别在函数里 panic,
expr会转成"error: function 'xxx' panicked",掩盖真实原因 - 如果函数要访问数据库,别直接注入 db 实例(生命周期难管),改用 closure 封装:
func(db *sql.DB) interface{} { return func(id int) bool { ... } }
复杂规则往往不是语法问题,而是变量作用域混乱、类型隐式转换失败、或函数执行副作用没隔离。先跑通一个带函数的简单表达式,再加嵌套和缓存,比一开始画 UML 图实在得多。









