资讯详情

15-A-解释器模式详解

📅 2026/10/18 1:56:28 | 华诺云谱 👁 阅读
15-A-解释器模式详解
15-A-解释器模式详解关键词解释器、文法树、表达式求值、营销规则、规则引擎、QLExpress、Aviator解释器模式解决一个问题给一种小语言定义文法并把表达式解析成可求值的语法树。营销规则满100减20 AND 会员、搜索语法手机 -二手 价格3000都是小语言。本篇先讲清解释器模式本身再诚实回答生产上到底该不该手写。 术语速查表术语人话解释文法Grammar语言的规则什么符号能怎么组合抽象表达式Expression语法树节点的公共接口核心方法interpret()终结符表达式叶子节点如数字、变量amount 100非终结符表达式枝干节点如 AND/OR组合子表达式上下文Context求值时需要的变量环境订单金额、用户等级规则引擎解释器思想的工程化产品QLExpress/Aviator/Drools一、业务场景营销优惠规则表达式运营在后台配置优惠条件要求支持amount 100 AND vipLevel 3 amount 200 OR (vipLevel 5 AND orderCount 10)如果每种组合都写 Java 代码运营每改一次规则就要发一次版——规则必须变成数据字符串运行时解析求值这就是解释器模式的地盘。二、模式结构组合左右子表达式组合左右子表达式Expression 接口interpret(ctx)AndExpression非终结符OrExpression非终结符CompareExpression终结符amount 100非终结符AND/OR持有子表达式——语法树本身就是组合模式见 08-A 的规则组解释器 组合模式 interpret()求值语义。三、完整实现迷你规则解释器抽象表达式与上下文/** 求值上下文变量环境 */publicclassRuleContext{privatefinalMapString,ObjectvarsnewHashMap();publicRuleContextset(Stringkey,Objectvalue){vars.put(key,value);returnthis;}publicBigDecimalgetNumber(Stringkey){Objectvvars.get(key);if(vnull)thrownewIllegalArgumentException(规则变量缺失: key);returnnewBigDecimal(v.toString());}}/** 抽象表达式语法树节点接口 */publicinterfaceRuleExpression{booleaninterpret(RuleContextctx);}终结符表达式比较条件/** amount 100 这类原子条件 */publicclassCompareExpressionimplementsRuleExpression{privatefinalStringvarName;// amountprivatefinalStringoperator;// privatefinalBigDecimalvalue;// 100publicCompareExpression(StringvarName,Stringoperator,BigDecimalvalue){this.varNamevarName;this.operatoroperator;this.valuevalue;}Overridepublicbooleaninterpret(RuleContextctx){BigDecimalactualctx.getNumber(varName);intcmpactual.compareTo(value);returnswitch(operator){case-cmp0;case-cmp0;case-cmp0;case-cmp0;case-cmp0;default-thrownewIllegalArgumentException(不支持的操作符: operator);};}}非终结符表达式AND / ORpublicclassAndExpressionimplementsRuleExpression{privatefinalRuleExpressionleft;privatefinalRuleExpressionright;publicAndExpression(RuleExpressionleft,RuleExpressionright){this.leftleft;this.rightright;}Overridepublicbooleaninterpret(RuleContextctx){// 短路求值左边 false 就不用算右边returnleft.interpret(ctx)right.interpret(ctx);}}publicclassOrExpressionimplementsRuleExpression{privatefinalRuleExpressionleft;privatefinalRuleExpressionright;publicOrExpression(RuleExpressionleft,RuleExpressionright){this.leftleft;this.rightright;}Overridepublicbooleaninterpret(RuleContextctx){returnleft.interpret(ctx)||right.interpret(ctx);}}解析器字符串 → 语法树/** 极简解析器支持 cond AND cond、cond OR cond、括号嵌套 */publicclassRuleParser{privatefinalListStringtokens;privateintpos;publicRuleParser(Stringrule){// 分词按空格切括号单独成 token生产要用真正的词法分析this.tokenstokenize(rule);}publicRuleExpressionparse(){RuleExpressionexprparseOr();if(postokens.size()){thrownewIllegalArgumentException(规则解析失败多余 token: tokens.get(pos));}returnexpr;}/** 优先级OR 最低 → AND → 括号/原子条件递归下降解析 */privateRuleExpressionparseOr(){RuleExpressionleftparseAnd();while(postokens.size()OR.equals(tokens.get(pos))){pos;leftnewOrExpression(left,parseAnd());}returnleft;}privateRuleExpressionparseAnd(){RuleExpressionleftparsePrimary();while(postokens.size()AND.equals(tokens.get(pos))){pos;leftnewAndExpression(left,parsePrimary());}returnleft;}privateRuleExpressionparsePrimary(){Stringtokentokens.get(pos);if((.equals(token)){pos;RuleExpressionexprparseOr();expect());returnexpr;}// 原子条件amount 100三个 tokenStringvartoken;Stringoptokens.get(pos1);BigDecimalvalnewBigDecimal(tokens.get(pos2));pos3;returnnewCompareExpression(var,op,val);}privatevoidexpect(Stringt){if(!t.equals(tokens.get(pos))){thrownewIllegalArgumentException(期望 t 但遇到 tokens.get(pos));}pos;}privateListStringtokenize(Stringrule){// 简化版分词生产建议用 ANTLR 或成熟规则引擎returnArrays.stream(rule.replaceAll(([()]), $1 ).split(\\s)).filter(s-!s.isEmpty()).toList();}}使用方publicclassPromotionService{/** 规则树缓存同一规则字符串只解析一次解析比求值贵得多 */privatefinalCacheString,RuleExpressionruleCacheCaffeine.newBuilder().maximumSize(1000).build();publicbooleanhitPromotion(StringruleText,Orderorder){// (1) 解析带缓存RuleExpressiontreeruleCache.get(ruleText,k-newRuleParser(k).parse());// (2) 构造上下文RuleContextctxnewRuleContext().set(amount,order.getAmount()).set(vipLevel,order.getVipLevel()).set(orderCount,order.getUserOrderCount());// (3) 求值returntree.interpret(ctx);}}// 运营配置的规则字符串Stringruleamount 100 AND ( vipLevel 3 OR orderCount 10 );booleanhitpromotionService.hitPromotion(rule,order);四、诚实的建议生产上别手写解释器手写解释器的完整成本清单词法分析tokenize要处理引号、转义、中文语法分析要处理优先级、结合性、括号嵌套错误提示要能告诉运营第几个字符写错了安全表达式注入、死循环、超大数运算性能解析缓存、求值优化。成熟替代品方案特点适用Aviator轻量、高性能、编译成字节码规则求值首选QLExpress阿里支持自定义操作符、宏电商营销规则淘宝同款MVEL / SpELSpring 生态内置SpEL简单表达式、注解里用Drools完整规则引擎RETE 算法规则数量巨大、需要规则流ANTLR语法解析器生成器真要自定义一门语言// Aviator 版三行顶上面三百行ExpressionexpAviatorEvaluator.compile(ruleText,true);// true 编译缓存Booleanhit(Boolean)exp.execute(Map.of(amount,order.getAmount(),vipLevel,order.getVipLevel()));什么时候才手写文法极简且固定如只支持AND连接的等值匹配、不想引依赖、或教学目的。面试讲解释器模式重点讲文法树 interpret 递归求值的思想然后主动说生产我会用 Aviator/QLExpress——这是加分而不是减分。五、框架中的同款实现框架应用说明JDKjava.util.regex.Pattern正则就是小语言Pattern 内部是语法树SpringSpELExpressionParserValue(#{...})、Security 的PreAuthorizeMyBatis动态 SQL 的 OGNL 表达式if teststatus ! null里的 testSQL 引擎任何 SQL 解析器Calcite/Druid ParserSQL 是最大的小语言解析→AST→执行Drools/Aviator/QLExpress规则引擎解释器思想的工程化六、常见误用与生产事故误用用解释器模式做配置化只有两三种固定规则形态却上了完整解释器——维护成本远大于收益。规则形态固定时用策略模式 配置参数见 21-A策略选逻辑配置传参数比自由文法可控得多。事故一规则解析无缓存大促 CPU 打满每次请求都new RuleParser(rule).parse()——解析字符串切分 建树比求值贵 50 倍。大促 QPS 上来后 CPU 100%RT 飙升。修复规则树缓存规则字符串 → 编译好的 ExpressionCaffeine 带上限规则变更时主动失效。编译一次执行多次是所有表达式引擎的铁律。事故二运营配错规则全量用户命中 0 元购规则字符串amount 0本想写amount 100少打两个字符——所有订单都命中满 0 减 50。修复规则保存时语法校验 语义预检用样例订单试算展示命中结果让运营确认优惠金额上限硬编码兜底单笔优惠 ≤ 订单金额的 50%超出拒绝规则变更走审批 灰度先 1% 流量验证命中率分布命中率突变告警某规则命中率从 5% 跳到 90% 立刻通知——规则是改一个字符影响全站的高危配置同款教训见 09-04-B-监控告警体系面试与生产事故实战 的配置变更事故。七、总结解释器的核心为小语言定义文法表达式解析成语法树interpret()递归求值。结构本质语法树 组合模式非终结符组合终结符求值就是树的递归遍历。生产建议文法复杂或会演化 → 直接用 Aviator/QLExpress/SpEL手写只适合极简固定文法。两条铁律编译一次执行多次解析必须缓存规则是高危配置校验 兜底 灰度 命中率告警。面试姿势讲清文法树 递归求值思想主动说明生产用规则引擎替代——展示的是判断力。上一篇14-A-命令模式详解下一篇16-A-迭代器模式详解
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑