解释器模式中的 Context 有什么作用?
简化版
Context 保存解释表达式所需的外部环境,例如变量值、用户信息、配置、函数表和中间状态。表达式描述规则,Context 提供数据,同一表达式在不同 Context 下可以得到不同结果。
详细版
Context 常保存:
- 变量名到值的映射。
- 当前用户属性。
- 运行环境参数。
- 函数或操作符注册表。
- 解释过程中的临时数据。
例如表达式 age > 18 本身不包含具体用户年龄。解释时从 Context 取出 age,再和 18 比较。
Context 的好处是让表达式对象和具体数据解耦。表达式可以复用,Context 可以替换。
设计 Context 时要注意线程安全、只读性、变量缺失处理和类型转换。
完整版教学
一、表达式和数据为什么要分开
表达式是一条规则,Context 是规则运行时的数据。
同一条规则 score >= 60,在学生 A 的 Context 中可能为 true,在学生 B 的 Context 中可能为 false。规则不变,数据变了,结果就变。
这就是 Context 的价值。
二、Context 的常见实现
简单实现可以是 Map:
class Context {
private Map<String, Object> variables;
public Object get(String name) {
return variables.get(name);
}
}
复杂实现可能包含类型系统、函数调用、权限信息、时间、区域、租户信息等。
三、Context 设计要注意什么
变量缺失时应该怎么处理?返回 null、抛异常,还是使用默认值?不同业务选择不同。
类型转换也很重要。字符串 "18" 和数字 18 是否能比较?布尔值如何解析?这些规则如果不统一,表达式结果会不稳定。
四、常见误区与工程判断
不要让表达式节点到处访问数据库或外部服务。解释器模式中的 Context 应尽量提供解释所需数据,表达式节点专注解释逻辑。
工程中如果表达式解释过程中不断远程调用,性能和可预测性都会很差。更好的方式是提前准备上下文数据。
五、Context 不应该变成万能对象
Context 的作用是提供解释过程中需要的外部信息,例如变量值、环境参数、函数表、运行时配置。它不是用来塞所有业务服务的容器。如果 Context 过大,Expression 会对各种外部对象产生强依赖,解释器会变得难测试、难复用。
设计 Context 时可以遵循两个原则。第一,只放解释表达式真正需要的信息,不放和语法执行无关的业务流程。第二,尽量让 Context 的接口表达语义,比如 getVariable("age")、hasPermission("admin"),而不是把内部 Map 直接暴露出去让表达式随意操作。
如果表达式执行需要修改上下文,也要明确副作用边界。纯规则判断最好让解释过程无副作用;如果是脚本执行类 DSL,需要修改状态,就要考虑执行顺序、错误回滚、并发隔离等问题。面试里能说到这点,说明你理解的是运行模型而不是类名。
六、用工程约束检验答案
同一 AST price * rate 可分别在 {price:100, rate:0.8} 和 {price:200, rate:0.9} 下得到 80 与 180。表达式树不变,Context 提供本次求值数据;这也是 AST 可缓存而请求上下文不能错误共享的原因。
| 检查项 | 核心判断 | 工程含义 |
|---|---|---|
| 变量表 | name -> typed value | 查找 price、rate |
| 环境能力 | 时区、函数注册表 | 必须按白名单暴露 |
| 执行状态 | 错误、预算、调用深度 | 防止无限或过量计算 |
把关键关系压缩成一条可复述的路径:
immutable AST
+ request Context A -> 80
+ request Context B -> 180
Context 生命周期通常不跨请求共享
Context 应是最小、明确、可测试的运行环境,不能演化成让所有表达式随意访问服务的万能容器。
落地前可以再按下面 3 步复核:
- 先说明“变量表”的核心机制:name -> typed value;再交代边界:查找 price、rate。
- 接着分析“环境能力”:时区、函数注册表;不能遗漏对应代价或结果:必须按白名单暴露。
- 最后用“执行状态”检查方案:错误、预算、调用深度;验收时确认防止无限或过量计算。
这三项构成完整判断链:先讲清变量表,再说明环境能力,最后用执行状态检验实现是否越界。
面试中若能给出违反“防止无限或过量计算”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。
七、常见误区与追问
- 误区:只看到“变量表”就认为方案成立。 必须同时说明核心机制“name -> typed value”和工程边界“查找 price、rate”。
- 误区:把“环境能力”当成无条件结论。 只有在“时区、函数注册表”成立时,才能据此讨论“必须按白名单暴露”。
- 追问:Context 可以可变吗? 可以,但副作用会降低可预测性;纯求值优先使用只读上下文。
- 追问:变量缺失返回 null 吗? 应采用明确的错误、Optional 或领域默认值,避免隐式空值传播。
- 追问:能把数据库连接放进去吗? 技术上能,但会让解释产生隐蔽 I/O,通常应先准备数据或限制能力。
- 追问:多线程能共享 Context 吗? 只有不可变且无请求状态时才安全。
- 追问:AST 缓存要连 Context 一起缓存吗? 通常只缓存 AST,Context 应按本次请求创建。
八、加强记忆
记忆时抓住这条主线:Context 保存解释所需外部数据;表达式描述规则,Context 提供变量值;同一表达式可在不同 Context 下复用;Context 要处理变量缺失和类型转换。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。