解释器模式有哪些角色?执行流程是怎样的?
简化版
解释器模式包括抽象表达式、终结符表达式、非终结符表达式、上下文和客户端。流程是客户端根据语法构建表达式树,然后调用根表达式的解释方法,表达式节点递归解释子节点并结合上下文得到结果。
详细版
角色说明:
- AbstractExpression:定义
interpret(context)。 - TerminalExpression:处理最小语法单元,如变量、数字、布尔值。
- NonterminalExpression:处理组合规则,如加法、与、或、比较。
- Context:保存变量值、环境信息和解释过程状态。
- Client:负责构建表达式树并调用解释。
执行流程:
- 输入表达式或规则。
- 解析成表达式对象树。
- 创建 Context。
- 调用根节点
interpret(context)。 - 节点递归解释子表达式。
- 返回最终结果。
解释器模式通常把“解析”和“解释”分开,解析负责建树,解释负责执行语义。
完整版教学
一、抽象表达式
抽象表达式定义统一接口:
interface Expression {
boolean interpret(Context context);
}
所有具体表达式都实现这个接口。这样客户端只需要面向 Expression 编程。
二、终结符和非终结符
终结符表达式是不能继续拆分的单元。例如变量表达式、数字表达式、常量表达式。
非终结符表达式由其他表达式组合而成。例如 AndExpression 包含左右两个表达式,解释结果是左右表达式都为 true。
这种结构天然适合递归。
三、Client 为什么也重要
很多讲解会忽略 Client,但实际项目里构建表达式树很关键。字符串规则要先经过词法分析、语法分析,才能变成表达式树。
如果规则来源是配置或用户输入,解析阶段还要做错误提示和安全校验。
四、常见误区与工程判断
不要把解析字符串和解释执行混在一个巨大方法里。这样一开始简单,后面加规则会越来越乱。
工程中可以先把表达式对象模型设计清楚,再决定解析方式。简单场景可以手写解析,复杂场景用 ANTLR、表达式引擎或规则引擎。
五、从输入到结果要分三层理解
解释器模式的完整流程可以拆成三层:第一层是语法建模,把每种语法规则抽象成 Expression;第二层是表达式树构建,把输入规则解析成对象结构;第三层是解释执行,从根表达式开始递归调用 interpret(context) 得到结果。
很多面试答案只讲 Expression 和 Context,却忽略“表达式树从哪里来”。如果规则直接写在代码里,客户端可以手动 new 出表达式树;如果规则来自配置文件或用户输入,就必须有解析阶段。解释器模式本身不等于解析器,它主要描述解析后的表达式对象如何执行。
回答时可以把角色串起来:TerminalExpression 处理最小语法单元,NonTerminalExpression 组合子表达式,Context 提供解释所需的外部变量,Client 或 Parser 负责构建表达式树。这样就能把静态类图讲成动态流程。
六、用工程约束检验答案
完整流程至少有 3 段:词法器把 age >= 18 转成 token,解析器构造比较表达式,Expression 再从 Context 读取 age 求值。若 age=20,结果为 true;Context 并不负责识别 >= 的语法。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| Client/Parser | 把输入构造成表达式树 | 处理语法结构 |
| Expression | 实现解释契约 | 递归计算语义 |
| Context | 提供 age=20 等运行数据 | 与 AST 分离 |
把关键关系压缩成一条可复述的路径:
"age >= 18"
-> [IDENT, GE, NUMBER]
-> GreaterEqual(Variable(age), 18)
-> interpret({age:20}) = true
角色回答不能漏掉“谁构建 AST”;GoF 结构中的 Client 往往承担这一步,生产系统通常交给专门 Parser。
落地前可以再按下面 3 步复核:
- 先说明“Client/Parser”的核心机制:把输入构造成表达式树;再交代边界:处理语法结构。
- 接着分析“Expression”:实现解释契约;不能遗漏对应代价或结果:递归计算语义。
- 最后用“Context”检查方案:提供 age=20 等运行数据;验收时确认与 AST 分离。
这三项构成完整判断链:先讲清Client/Parser,再说明Expression,最后用Context检验实现是否越界。
面试中若能给出违反“与 AST 分离”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“Client/Parser”就认为方案成立。 必须同时说明核心机制“把输入构造成表达式树”和工程边界“处理语法结构”。
- 误区:把“Expression”当成无条件结论。 只有在“实现解释契约”成立时,才能据此讨论“递归计算语义”。
- 追问:AbstractExpression 一定是抽象类吗? 不一定,Java 中常用接口表达统一 interpret 契约。
- 追问:Parser 属于经典五角色吗? 经典图常由 Client 构建语法树,但工程上通常显式拆出 Parser。
- 追问:谁处理语法错误? 解析阶段负责报告非法 token、缺失括号等语法错误。
- 追问:谁处理变量不存在? 解释阶段通过 Context 查找并返回错误或采用明确默认策略。
- 追问:AST 能否复用? 可以,同一不可变 AST 可配不同 Context 多次求值。
八、加强记忆
记忆时抓住这条主线:Expression 定义解释接口;终结符是叶子,非终结符是组合节点;Context 保存外部变量;执行过程通常是表达式树递归解释。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。