什么是解释器模式?它解决了什么问题?
简化版
解释器模式为某种语言或表达式定义语法表示,并提供解释执行规则。它适合语法简单、规则稳定、需要解释表达式的场景,例如规则表达式、简单计算器、权限表达式、查询条件解析等。
详细版
解释器模式的核心是把语法规则表示成类结构。
典型角色:
- AbstractExpression:抽象表达式,定义解释接口。
- TerminalExpression:终结符表达式,表示不可再拆的语法单元。
- NonterminalExpression:非终结符表达式,组合多个表达式。
- Context:上下文,保存解释所需变量和环境。
- Client:构建语法树并触发解释。
它解决的问题是:当某类表达式有固定语法,并且需要频繁解释执行时,可以把语法规则对象化,让每种规则都有独立解释逻辑。
缺点是类数量容易膨胀,复杂语法不适合手写解释器模式,通常应使用解析器生成器或成熟表达式引擎。
完整版教学
一、为什么需要解释器模式
很多系统都会出现“小语言”。例如权限系统中的 role=admin AND department=tech,规则引擎中的 age > 18 && score > 60,计算器中的 1 + 2 * 3。
这些字符串不是普通文本,而是有语法和含义。解释器模式的思路是:把表达式解析成对象结构,再让每个对象负责解释自己。
这样语法规则就从散落的 if else 中抽出来,形成清晰的表达式类层级。
二、表达式树是核心
表达式通常会被构造成树。
例如 a AND b 可以表示为:
AND
/ \
a b
叶子节点是终结符表达式,组合节点是非终结符表达式。解释时从根节点开始递归解释,最终得到结果。
三、Context 的作用
Context 保存解释过程中需要的外部信息。例如变量值、用户属性、环境参数。
表达式本身描述规则,Context 提供实际数据。这样同一个表达式可以在不同上下文中解释出不同结果。
例如表达式是 age > 18,Context 中 age 为 20 时为 true,age 为 16 时为 false。
四、常见误区与工程判断
解释器模式不是让你手写一个完整编程语言。它适合小而稳定的语法。如果语法包含复杂优先级、作用域、函数、类型系统、错误恢复,就应该考虑成熟解析器或规则引擎。
工程中使用解释器模式前要问:语法是否简单?规则是否稳定?表达式数量是否可控?如果答案是否定的,手写解释器会变成维护噩梦。
五、解释器模式的本质是把语法结构对象化
解释器模式不是简单地“解释一段字符串”,而是先把某种小语言的规则拆成表达式对象,再通过这些对象组合成语法树,最后递归解释。也就是说,它关注的是“语法规则如何映射成类结构”,而不是只关注字符串处理。
面试官如果追问“为什么不用 if else 解析规则”,可以回答:当规则很少时,if else 没问题;但当规则有层级、组合、嵌套、递归时,把规则对象化更容易扩展和理解。例如布尔表达式里的变量、常量、与、或、非,每一种语法单元都能成为一个 Expression。
也要主动说明边界:解释器模式只适合语法相对简单、规则规模可控的 DSL。如果语法复杂到接近编程语言,应使用成熟 parser、语法分析器生成工具或规则引擎,而不是手写一堆解释器类。
六、用工程约束检验答案
以 1 + 2 * 3 为例,先按优先级构造 AST,结果应是 7 而不是 9;随后每个表达式节点递归求值。这个例子同时说明解释器模式不只是字符串切分,词法、解析和解释是不同阶段。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| TerminalExpression | 数字、变量等叶子 | 直接从自身或 Context 取值 |
| NonterminalExpression | 加法、乘法等组合规则 | 递归解释子表达式 |
| Context | 变量和运行环境 | 不承载语法树结构 |
把关键关系压缩成一条可复述的路径:
source -> tokens
tokens -> parser -> AST
AST + Context -> interpret
结果:1 + 2 * 3 = 7
解释器模式描述语法对象及解释行为,不自动解决词法分析、优先级和错误恢复。
落地前可以再按下面 3 步复核:
- 先说明“TerminalExpression”的核心机制:数字、变量等叶子;再交代边界:直接从自身或 Context 取值。
- 接着分析“NonterminalExpression”:加法、乘法等组合规则;不能遗漏对应代价或结果:递归解释子表达式。
- 最后用“Context”检查方案:变量和运行环境;验收时确认不承载语法树结构。
这三项构成完整判断链:先讲清TerminalExpression,再说明NonterminalExpression,最后用Context检验实现是否越界。
面试中若能给出违反“不承载语法树结构”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“TerminalExpression”就认为方案成立。 必须同时说明核心机制“数字、变量等叶子”和工程边界“直接从自身或 Context 取值”。
- 误区:把“NonterminalExpression”当成无条件结论。 只有在“加法、乘法等组合规则”成立时,才能据此讨论“递归解释子表达式”。
- 追问:它必须接收字符串吗? 不必须,Expression 通常解释已构建的 AST,字符串只是常见输入来源。
- 追问:每条文法都建一个类吗? 经典结构如此映射,但工程实现可用枚举、组合节点等方式减少类数量。
- 追问:为什么适合 DSL? 小型稳定文法容易映射成可组合对象,规则含义也较清晰。
- 追问:为什么不适合完整语言? 复杂语法、类型系统和错误恢复会造成类爆炸与维护困难。
- 追问:结果只能是布尔值吗? 不是,可以是数字、字符串、领域对象或带错误信息的结果。
八、加强记忆
记忆时抓住这条主线:解释器模式把语法规则表示成类;表达式树是它的核心结构;Context 提供解释所需环境;它适合简单 DSL,不适合复杂语言。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。