Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

22_解释器模式 Interpreter Pattern

1. 基本信息

中文名称: 解释器模式

英文名称: Interpreter Pattern

模式类型: 行为型设计模式

Rust 中常见实现方式: enum 表达式树、match 递归解释、trait 表达式接口、AST、递归求值

解释器模式的核心是:为某种语言、表达式或规则定义语法表示,并提供解释执行这些语法结构的方法。

在 Rust 中,解释器模式通常不需要像传统 OOP 那样为每种语法节点都创建一个类,而是更常用:

enum 表达不同语法节点
match 分发不同解释逻辑
递归函数执行表达式求值

代码示例


2. 模式核心思想

解释器模式的核心思想是:

把一种规则、表达式或简单语言表示成程序中的结构,
然后通过解释器逐步解析和执行这些结构。

它关注的问题是:

如何表示一套语法?
如何把语法结构转换成程序可以执行的逻辑?
如何对表达式进行求值或解释?

例如:

1 + 2 * 3
age > 18 && active == true
A AND B OR C
price > 100

这些都可以看作某种表达式或规则。

解释器模式要做的就是:把这些表达式抽象成结构,再写解释逻辑执行它们。


3. 这个模式解决什么问题

解释器模式主要解决的是:当系统中存在一套固定规则、表达式或小型语言时,如何把它们结构化并执行。

在实际开发中,可能会遇到:

1. 数学表达式求值
2. 条件规则判断
3. 查询过滤表达式
4. 简单脚本语言
5. 配置规则解释
6. 正则表达式风格的匹配规则
7. DSL 领域特定语言

如果不用解释器模式,可能会把规则写成一堆字符串判断或 if/else,导致规则扩展困难。

解释器模式的作用是:

把规则从散乱判断变成结构化表达式,
再用统一解释逻辑执行这些表达式。

4. 不使用这个模式会怎样

如果不使用解释器模式,可能出现:

1. 规则逻辑散落在大量 if/else 中。

2. 表达式结构不清晰,难以扩展。

3. 新增规则时需要修改大量业务代码。

4. 字符串规则容易写错,缺少类型约束。

5. 复杂表达式难以组合和复用。

例如:

如果用户年龄大于 18,并且账号处于激活状态,或者用户是管理员,则允许访问。

如果直接写业务判断,逻辑可能越来越复杂。 如果使用解释器模式,可以把它表示成表达式树:

Or(
    And(AgeGreaterThan(18), IsActive),
    IsAdmin
)

这样规则本身就变成了可组合的数据结构。


5. 传统面向对象中的实现思路

在传统 OOP 中,解释器模式通常包含以下角色:

1. AbstractExpression:抽象表达式接口
2. TerminalExpression:终结符表达式
3. NonTerminalExpression:非终结符表达式
4. Context:解释时需要的上下文信息
5. Client:构造表达式并调用解释方法

典型结构是:

Expression
 └── interpret(context)

NumberExpression
 └── interpret(context)

AddExpression
 ├── left: Expression
 ├── right: Expression
 └── interpret(context)

也就是说,每一种语法节点都对应一个类,每个类实现自己的 interpret() 方法。

比如数学表达式:

1 + 2

可以表示成:

AddExpression(
    NumberExpression(1),
    NumberExpression(2)
)

然后通过递归调用 interpret() 得到结果。


6. Rust 中的实现思路

6.1 Rust 中通常怎么实现

Rust 中解释器模式最自然的实现方式通常是:

enum + match + 递归求值

例如一个简单数学表达式可以表示为:

enum Expr {
    Number(i32),
    Add(Box<Expr>, Box<Expr>),
    Sub(Box<Expr>, Box<Expr>),
    Mul(Box<Expr>, Box<Expr>),
}

impl Expr {
    fn eval(&self) -> i32 {
        match self {
            Expr::Number(n) => *n,
            Expr::Add(left, right) => left.eval() + right.eval(),
            Expr::Sub(left, right) => left.eval() - right.eval(),
            Expr::Mul(left, right) => left.eval() * right.eval(),
        }
    }
}

fn main() {
    let expr = Expr::Add(
        Box::new(Expr::Number(1)),
        Box::new(Expr::Mul(
            Box::new(Expr::Number(2)),
            Box::new(Expr::Number(3)),
        )),
    );

    println!("{}", expr.eval()); // 7
}

这个表达式对应的是:

1 + 2 * 3

解释过程是:

Add
├── Number(1)
└── Mul
    ├── Number(2)
    └── Number(3)

最终递归求值得到:

1 + 6 = 7

这里的 Expr 就是表达式树,也就是 AST。


6.2 和传统 OOP 写法相比有什么不同

和传统面向对象语言相比,Rust 的解释器模式有几个明显特点。

第一,Rust 更适合用 enum 表达语法节点。 传统 OOP 可能会为每一种表达式创建一个类,例如 NumberExpressionAddExpressionSubExpression。 Rust 中可以直接用一个 enum 表达所有表达式类型。

第二,Rust 使用 match 分发表达式解释逻辑。 不同表达式节点的解释逻辑可以集中在一个 match 中,结构清晰。

第三,Rust 的 Box 常用于构造递归结构。 因为表达式树是递归的,Expr 中又包含 Expr,需要用 Box<Expr> 让递归类型大小在编译期可确定。

第四,Rust 的类型系统可以约束表达式结构。 例如 Expr::Number(i32) 明确表示数值节点,Expr::Add(Box<Expr>, Box<Expr>) 明确表示二元加法节点,不容易写出非法结构。

第五,Rust 不一定需要 trait object。 如果语法类型是固定的,用 enum + match 通常比 Box<dyn Expression> 更简单、更高效。


6.3 Rust 中是否有更自然的替代写法

在 Rust 中,解释器模式可以根据复杂程度选择不同写法。

如果表达式种类固定,推荐:

enum + match

如果表达式节点类型需要由外部扩展,可以考虑:

trait + Box<dyn Expression>

如果表达式来自文本输入,还需要增加:

词法分析 lexer
语法分析 parser
AST 构建
解释执行 eval

如果规则非常简单,不需要完整解释器,可以直接使用:

闭包
函数组合
match
简单规则表

所以不是所有规则判断都需要解释器模式。 只有当规则本身逐渐形成“语言”或“表达式结构”时,解释器模式才更有价值。


7. Rust 中涉及的语言特性

解释器模式在 Rust 中主要涉及以下语言特性:

1. enum
2. match
3. Box
4. 递归数据结构
5. struct
6. impl
7. trait
8. Result
9. Option
10. 所有权与借用

这些特性分别解决的问题是:

enum:表达不同语法节点
match:根据节点类型分发解释逻辑
Box:构造递归表达式树
impl:定义 eval 或 interpret 方法
trait:支持可扩展表达式接口
Result:表达解析或解释失败
Option:表达可选值或查找结果
所有权:管理表达式树节点生命周期

其中最核心的是:

enum + match + Box

8. 性能与工程代价

解释器模式的性能主要取决于表达式树规模和解释方式。

如果使用 enum + match

1. 分发成本较低。
2. 不需要动态分发。
3. 表达式结构清晰。
4. 适合固定语法的小型解释器。

如果使用 Box<dyn Expression>

1. 可以扩展更多表达式类型。
2. 但会产生动态分发开销。
3. 节点通常需要堆分配。
4. 类型结构更灵活,但调试复杂度更高。

解释器模式本身通常不会像编译器那样生成高效机器代码,它更偏向“运行时解释执行”。

所以它适合:

规则系统
配置语言
小型 DSL
简单表达式求值

但不适合直接承担高性能计算核心。

如果表达式非常频繁执行,可以考虑:

1. 预编译表达式
2. 缓存 AST
3. 简化表达式树
4. 转换为闭包或函数
5. 使用编译器或 JIT 思路

一句话总结:

解释器模式结构清晰、扩展方便,但复杂表达式和频繁解释可能带来运行时开销。

9. 典型应用场景

解释器模式适合用于有固定语法或规则表达式的场景。

常见应用包括:

1. 数学表达式求值
2. 规则引擎
3. 查询表达式解析
4. 配置条件判断
5. 简单脚本语言
6. DSL 领域特定语言
7. 正则表达式解释
8. 模板语言解析
9. 权限规则判断
10. 编译器或解释器前端

在 Rust 项目中,解释器模式可能出现在:

配置规则系统
命令行表达式解析
编译器 toy project
查询过滤语言
嵌入式规则判断
测试条件表达式

例如:

age > 18 && country == "CN"

可以被解析成表达式树,再用解释器结合用户上下文判断结果。


10. 和相似模式的区别

解释器模式容易和访问者模式、组合模式、策略模式、状态模式混淆。

10.1 解释器模式和组合模式

二者都可能使用树形结构,但关注点不同。

组合模式:关注如何组织整体和部分,重点是树形结构的统一处理。
解释器模式:关注如何解释和执行语法结构,重点是表达式求值。

例如:

文件夹和文件组成目录树:组合模式
数学表达式组成表达式树并求值:解释器模式

解释器模式经常会使用组合结构,但目的不是单纯组织树,而是解释树。


10.2 解释器模式和访问者模式

二者都常用于 AST,但目的不同。

解释器模式:在语法节点中定义解释逻辑。
访问者模式:把对语法节点的操作从节点结构中分离出来。

如果只有一种主要操作,例如求值,可以直接用解释器模式。

如果需要对同一 AST 做很多操作,例如:

求值
打印
类型检查
代码生成
优化

访问者模式可能更合适。


10.3 解释器模式和策略模式

策略模式关注的是可替换算法。

解释器模式关注的是表达式结构的解释执行。

策略模式:选择哪种算法。
解释器模式:解释一套语法规则。

例如:

选择快速排序或归并排序:策略模式
解释 “1 + 2 * 3” 的语法树:解释器模式

10.4 解释器模式和状态模式

状态模式关注对象不同状态下的行为变化。

解释器模式关注表达式或语言规则的执行。

状态模式:对象现在处于什么状态,所以怎么行动。
解释器模式:这条表达式是什么意思,执行结果是什么。

例如:

订单已支付,所以可以发货:状态模式
解析并执行 “paid == true && stock > 0”:解释器模式

11. 使用该模式的优点

解释器模式的优点主要有:

1. 可以把规则或表达式结构化。
2. 适合实现小型语言或 DSL。
3. 表达式节点可以组合,扩展能力较强。
4. 解释逻辑清晰,便于理解语法含义。
5. Rust 中使用 enum + match 表达 AST 很自然。
6. 可以把复杂判断从业务代码中抽离出来。

从工程角度看,解释器模式的价值在于:

把“写死在代码里的判断逻辑”变成“可表达、可组合、可解释的规则结构”。

12. 使用该模式的代价

解释器模式的代价主要包括:

1. 需要设计表达式结构,前期成本较高。
2. 复杂语法需要词法分析和语法分析。
3. 表达式树过大时解释开销较高。
4. 如果语法频繁变化,维护成本会上升。
5. 对简单条件判断使用解释器会显得过度设计。
6. 错误处理、调试信息和定位机制需要额外设计。

在 Rust 中,如果使用递归 AST,还需要注意:

Box 递归结构
所有权转移
借用关系
错误返回 Result

这些会比普通业务判断更复杂。


13. 什么时候不应该使用

以下情况不适合使用解释器模式:

1. 规则非常简单,普通 if/else 足够清晰。
2. 不需要表达式组合。
3. 规则不会扩展,也不会由外部配置。
4. 性能极端敏感,不适合运行时解释。
5. 语法复杂到接近完整编程语言,此时应考虑 parser/编译器框架。
6. 团队不需要 DSL,强行设计语言反而增加成本。

可以遵循一个简单原则:

如果只是几个条件判断,不需要解释器。
如果规则开始像一种“小语言”,可以考虑解释器模式。

14. 一个简单例子思路

场景: 简单数学表达式解释器

需求:

支持数字、加法、减法、乘法。
可以表达 1 + 2 * 3。
可以通过 eval() 求值。

处理流程:

1. 定义 enum Expr。
2. Expr::Number 表示数字。
3. Expr::Add 表示加法表达式。
4. Expr::Sub 表示减法表达式。
5. Expr::Mul 表示乘法表达式。
6. 使用 Box<Expr> 构造递归表达式树。
7. 使用 match 递归求值。

适合练习的 Rust 特性:

enum
match
Box
递归结构
impl
所有权
Result

后续可以扩展为:

变量表达式
布尔表达式
比较运算
上下文 Context
解析字符串输入

15. 总结一句话

解释器模式的本质是:

把规则、表达式或简单语言表示成程序中的语法结构,并提供解释执行这些结构的方法。

在 Rust 中,解释器模式通常使用 enum + match + Box 构造表达式树,再通过递归函数进行解释执行。它适合规则引擎、小型 DSL、表达式求值等场景,但不适合过于简单的条件判断或极端性能敏感的核心路径。