GoF设计模式——解释器模式

发布时间:2026/7/22 15:20:11
GoF设计模式——解释器模式 为什么需要解释器模式假设营销系统要判断用户是否符合优惠活动资格规则是用户年龄 18 且 积分 100。最直接的写法是 if-elsepublic boolean check(User user) {return user.getAge() 18 user.getScore() 100;}看起来简单但规则会变–加一个或 VIP 用户可参与分支、改个阈值、加一层嵌套 (A 或 B) 且 C每改一次就得改这段代码、重新发版。当规则要支持动态配置、让运营自己配时if-else 写到后面就成了无法维护的乱麻。问题症结在于把规则的语法和规则的执行焊在了一起。如果能先把规则抽象成一套文法每条规则用一个类表示运行时动态组合成树、递归执行加规则就只是加一个类–这就是解释器模式。概念解释器模式Interpreter Pattern是一种行为型设计模式核心思想是给定一个语言定义它的文法的一种表示并定义一个解释器使用该表示来解释语言中的句子。文法与AST要理解这个模式得先理解两个前置概念——文法和 AST。文法就是语法规则。要让计算机理解 3 4 * 2得告诉它规则一个表达式由若干项用加号连接一项由若干因子用乘号连接一个因子就是一个数字。用 BNF一种描述文法的记号写出来expr - term (‘’ term)*term - factor (’ factor)factor - NUMBERBNF 的精髓在于分层–每一层处理一种优先级乘法在更深的 term 层所以一定先算乘再算加靠层级结构而非约定决定计算顺序。把文法画成一棵树就是 AST抽象语法树。3 4 * 2 的 AST 长这样AddExpression / \ NumberExpr(3) MultiplyExpression / \ NumberExpr(4) NumberExpr(2)运算顺序从叶子往根先算 4 * 2 8再算 3 8 11。这种从叶子到根的过程天然适合递归。解释器模式就是把文法规则 AST 递归求值写成代码每条文法规则对应一个类每个节点自己知道怎么算自己组合起来就能解释整个表达式。详细可看文法、BNF、AST解释器模式解释器模式涉及四个角色AbstractExpression抽象表达式定义解释器的统一接口声明 interpret 方法TerminalExpression终结符表达式文法中不可再分的基本元素如数字、变量直接返回值或从上下文查值NonterminalExpression非终结符表达式由子表达式组合而成的复合节点如加法、AND通过递归调用子表达式的 interpret 完成求值Context上下文存储解释过程中的全局信息如变量值供终结符查询实现实现left/right 子表达式构建调用传入«interface»AbstractExpressioninterpret(Context) : ObjectTerminalExpression-value: Stringinterpret(Context) : ObjectNonterminalExpression-left: AbstractExpression-right: AbstractExpressioninterpret(Context) : ObjectContext-variables: Maplookup(String) : Objectassign(String, Object)Clientmain()图中各类之间的关系AbstractExpression 是所有节点的统一接口TerminalExpression 和 NonterminalExpression 都实现它。NonterminalExpression 用 left/right 持有两个子表达式引用–子表达式既可以是终结符也可以是另一个非终结符它不关心只调 interpret 让多态分派。Context 存储变量映射TerminalExpression 通过它查值。Client 负责按文法构建 AST把 Context 和根节点一起启动递归求值。实现解释器模式的核心是一个规则一个类 递归求值–每条文法规则对应一个 Expression 子类非终结符持有子表达式引用interpret 递归调用子节点。解析Parser属于客户端职责可手动构建 AST也可用 Parser 自动构建。基础实现用布尔表达式演示解释器模式。定义 BooleanExpression 接口声明 interpret(Context)ConstantExpression 作为终结符返回布尔常量VariableExpression 作为终结符从上下文查变量值AndExpression、OrExpression 作为非终结符持有左右子表达式递归求值。客户端构建布尔表达式 AST如 (x AND y) OR z并启动递归。import java.util.*;// 抽象表达式所有节点的统一接口interface BooleanExpression {boolean interpret(Context context);}// 终结符表达式布尔常量class ConstantExpression implements BooleanExpression {private boolean value;public ConstantExpression(boolean value) {this.value value;}public boolean interpret(Context context) {return value; // 直接返回常量值}}// 终结符表达式布尔变量从上下文查值class VariableExpression implements BooleanExpression {private String name;public VariableExpression(String name) {this.name name;}public boolean interpret(Context context) {return context.lookup(name); // 从上下文查变量值}}// 非终结符表达式ANDclass AndExpression implements BooleanExpression {private BooleanExpression left;private BooleanExpression right;public AndExpression(BooleanExpression left, BooleanExpression right) {this.left left;this.right right;}public boolean interpret(Context context) {return left.interpret(context) right.interpret(context);}}// 非终结符表达式ORclass OrExpression implements BooleanExpression {private BooleanExpression left;private BooleanExpression right;public OrExpression(BooleanExpression left, BooleanExpression right) {this.left left;this.right right;}public boolean interpret(Context context) {return left.interpret(context) || right.interpret(context);}}// 上下文存储布尔变量映射供终结符查询class Context {private MapString, Boolean variables new HashMap();public void assign(String key, boolean value) {variables.put(key, value);}public boolean lookup(String key) {return variables.get(key);}}// 客户端构建 AST 并执行class Client {public static void main(String[] args) {Context context new Context();context.assign(“x”, true);context.assign(“y”, false);context.assign(“z”, true);// 构建 (x AND y) OR z 的 ASTBooleanExpression ast new OrExpression(new AndExpression(new VariableExpression(“x”), new VariableExpression(“y”)),new VariableExpression(“z”));System.out.println(ast.interpret(context)); // (true AND false) OR true true}}角色对照AbstractExpression抽象表达式BooleanExpression声明 interpret(Context)TerminalExpression终结符表达式ConstantExpression布尔常量、VariableExpression布尔变量NonterminalExpression非终结符表达式AndExpression、OrExpression逻辑组合Context上下文Context存布尔变量映射关键点每个非终结符只关心自己这一层的规则不关心子表达式是什么类型–AndExpression 调 left.interpret(context) 时left 是常量、变量还是另一个 AND 它无所谓多态自动分派到正确实现。求值时从根节点 ast.interpret(context) 开始递归向下到叶子再逐层返回整个 (x AND y) OR z 就被解释成了 true。这就是 GoF 原版结构终结符返回值非终结符组合子表达式Context 存储变量映射。引入一个生活比喻规则求值像公司逐级审批。基层的终结符ConstantExpression、VariableExpression是办事员各自查条件是否满足并上报结论非终结符AndExpression、OrExpression是主管收集下属结论做逻辑汇总后继续上报。以基础实现的 (x AND y) OR z 为例xtrue、yfalse两个办事员查到值上报AndExpression 主管汇总得 false再上报给 OrExpression 总监和 ztrue 汇总得 true。每个节点只关心下属给我的结论和我怎么汇总不关心下属具体查了什么–这正是递归求值的本质。同样的思路换到业务场景营销系统要把 (age 18) AND (score 90) 这样的规则做成可配置的 AST。每条比较条件是终结符AND/OR 是非终结符事实数据放在上下文里。import java.util.*;// 抽象表达式规则interface RuleExpression {boolean interpret(RuleContext context);}// 终结符比较条件如 age 18class CompareExpression implements RuleExpression {private String field;private String operator;private int value;public CompareExpression(String field, String operator, int value) {this.field field;this.operator operator;this.value value;}public boolean interpret(RuleContext context) {int factValue context.getFact(field); // 从上下文取事实switch (operator) {case “”: return factValue value;case “”: return factValue value;case “”: return factValue value;case “”: return factValue value;case “”: return factValue value;default: throw new IllegalArgumentException(未知运算符: operator);}}}// 非终结符AND 组合class AndExpression implements RuleExpression {private RuleExpression left;private RuleExpression right;public AndExpression(RuleExpression left, RuleExpression right) {this.left left;this.right right;}public boolean interpret(RuleContext context) {return left.interpret(context) right.interpret(context);}}// 非终结符OR 组合class OrExpression implements RuleExpression {private RuleExpression left;private RuleExpression right;public OrExpression(RuleExpression left, RuleExpression right) {this.left left;this.right right;}public boolean interpret(RuleContext context) {return left.interpret(context) || right.interpret(context);}}// 上下文存储事实数据class RuleContext {private MapString, Integer facts new HashMap();public void setFact(String key, int value) {facts.put(key, value);}public int getFact(String key) {return facts.get(key);}}// 客户端构建优惠资格规则 AST 并执行class PromotionCheck {public static void main(String[] args) {// 规则(age 18) AND (score 90)RuleExpression rule new AndExpression(new CompareExpression(“age”, “”, 18),new CompareExpression(“score”, “”, 90));RuleContext ctx new RuleContext();ctx.setFact(“age”, 25);ctx.setFact(“score”, 95);System.out.println(rule.interpret(ctx)); // true}}角色对照AbstractExpression抽象表达式RuleExpression声明 interpret(RuleContext)TerminalExpression终结符表达式CompareExpression一个比较条件NonterminalExpression非终结符表达式AndExpression、OrExpression组合子规则Context上下文RuleContext存事实数据关键点规则被表示成一棵 ASTAndExpression/OrExpression 只负责把子规则的布尔结果做逻辑运算不关心子规则比的是什么字段。新增NOT规则只需加一个 NotExpression 类老规则代码一行不改–这就是开闭原则。规则可以配置化存储如 JSON 描述的规则树运行时由 Parser 还原成 AST 执行运营改规则不用改代码。总结本质把文法规则表示成一个个表达式类用 AST 组织起来通过递归求值解释整个句子。什么时候用问题能被明确抽象成文法表达式、规则、DSL、协议格式文法规则会频繁扩展希望加规则只加类、不改老代码规则需要运行时动态配置和组合而非编译时写死什么时候不用文法复杂、规则极多–类会爆炸式增长难以管理对性能要求高–递归调用和大量对象创建有开销规则简单且固定直接 if-else 或现成引擎更清爽追求效率而非扩展性解释器模式的递归求值不如直接编译简单记忆文法拆成树节点各自算叶子先出值非叶递归往上返一规则一个类组合起来能解释。相似模式区分总览模式 核心意图 典型场景解释器 定义文法并表示为 AST递归解释执行 表达式求值、规则引擎、DSL组合 将对象组合成树形结构统一处理叶子和容器 文件系统、菜单树、组织架构访问者 不修改元素类的前提下定义新操作 AST 类型检查、代码生成策略 封装可互换的算法运行时选择其一执行 支付方式、排序算法切换简单记忆解释器造树求值组合管结构访问者管操作策略管替换。解释器 vs 组合模式两者都用树形结构但意图不同维度 解释器模式 组合模式核心意图 解释语言/文法递归求值 表示部分-整体的层次结构统一操作结构差异 叶子和组合节点有不同的解释逻辑 叶子和组合节点实现相同接口对外透明关注点 文法规则的解析和执行 对象树的遍历和操作统一化典型场景 表达式求值、规则引擎 文件系统、UI 组件树、组织架构逐步区分法如果树形结构是为了计算或解释某种语义表达式求值、规则判断- 选解释器如果树形结构是为了统一处理单个对象和组合对象文件/文件夹、按钮/面板- 选组合简单记忆口诀“组合管结构解释管语义”。推荐解释器模式的底层结构确实用了类似组合模式的树但解释器额外多了文法规则和递归求值的语义层。只组合不解释就用组合模式既要建树又要按文法求值才用解释器。解释器 vs 访问者模式两者都涉及树形结构的遍历和操作维度 解释器模式 访问者模式核心意图 定义语言文法并解释执行 在不修改元素类的前提下添加新操作结构差异 每个节点自己知道如何 interpret 操作逻辑集中在 Visitor 中节点只负责 accept关注点 “怎么算”–语法规则的执行 “算什么”–对固定结构执行多种不同操作典型场景 表达式求值、DSL 解析 编译器的类型检查、代码生成、AST 变换逐步区分法如果需要定义一种语言的语法规则并执行它 - 选解释器如果 AST 结构已固定想在不改节点类的情况下增加新的遍历操作 - 选访问者简单记忆口诀“解释器造树又求值访问者只管遍历做操作”。推荐解释器同时负责构建 AST 和执行语义访问者假设树已经存在只关心如何遍历和处理。编译器里两者常配合使用–解释器负责建树求值访问者负责后续的类型检查和优化变换。解释器 vs 策略模式两者都把变化的部分封装成类实际开发中一条规则一个策略类容易被误当成策略模式但结构完全不同维度 解释器模式 策略模式核心意图 定义文法规则可组合成树递归求值 封装可互换的算法运行时选一个执行结构差异 非终结符持有子表达式形成 AST 树 各策略平级独立互不持有由 Context 持有一个关注点 “规则怎么组合”–文法的层级与递归 “用哪个算法”–算法的替换与选择典型场景 表达式求值、规则引擎规则可嵌套 支付方式选择、排序算法切换逐步区分法如果规则之间能组合嵌套如 A AND (B OR C)需要树形结构递归求值 - 选解释器如果规则之间平级独立、互不组合只是运行时挑一个执行 - 选策略简单记忆口诀“解释器造树组合策略模式平行替换”。推荐当一条规则一个类但规则之间没有组合关系时用策略模式就够了别上解释器的树形结构。只有规则需要动态嵌套成 AST 才值得用解释器–解释器的复杂度换来的是组合能力。练习题目数学表达式计算器题目描述设计一个计算器用于解释用户输入的简单数学表达式。每个表达式由整数、加法操作符 、乘法操作符 * 组成元素之间用空格分隔。请使用解释器模式实现这个系统。解释器模式要求定义抽象表达式接口 Expression包含 int interpret() 方法终结符表达式对应数字非终结符表达式对应运算加法、乘法支持多位数如 10、135正确处理运算符优先级乘法优先于加法不允许使用 javax.script.ScriptEngine 等内置求值器运算逻辑必须封装在 Expression 子类的 interpret() 中不允许在解析器中用 if-else 做运算输入描述每行包含一个数学表达式表达式中包含整数、加法操作符和乘法操作符*。表达式中的元素之间用空格分隔。输出描述对于每个输入的数学表达式每行输出一个整数表示对应表达式的计算结果。输入示例2 35 * 23 4 * 210 20 * 3 510 * 2 3 * 5输出示例510117535提示本题对应的文法BNF 风格如下expr - term (‘’ term)*term - factor (’ factor)factor - NUMBER其中 NUMBER 表示一个整数。expr 调用 term、term 内部消化了所有乘法所以乘法优先于加法–优先级靠文法层级保证而非硬编码顺序。解题思路题目是解释器模式的典型应用–数学表达式是最常见的文法每条 BNF 规则对应一个 Expression 子类运算逻辑封装在 interpret() 里。难点不在解释器本身它只是递归求值而在 Parser 按文法构建 AST三个解析方法严格对应三层文法优先级通过层级嵌套自然体现。去掉解释器模式把运算塞进 Parser 的 if-else规则一多就成了面条代码也无法体现一规则一类的扩展性。