Java 23 种设计模式:从踩坑到精通 | 解释器模式 —— 自己动手写一个小语言解释器
Java 23 种设计模式:从踩坑到精通 | 解释器模式 —— 自己动手写一个小语言解释器
摘要:当需要解析特定格式的语言或表达式(如数学公式、SQL 语句、规则引擎),且语法规则可能频繁变化时,手写字符串处理会让代码迅速膨胀且难以扩展。解释器模式将每个语法规则表示为一个类,通过这些类的组合构建出抽象语法树(AST),从而将“解释”这个复杂过程拆解为一棵可递归求值的对象树。本文从四则运算计算器出发,完整讲解解释器模式的原理、UML、代码实现、优缺点,并与组合模式进行对比,结合 Java 正则表达式、Spring SpEL 等框架应用,帮你掌握“语言即对象树”的设计思想。
🗺️ 本文阅读地图(3 分钟速览)
- 为什么
switch解析表达式一定会炸?- 终结符 vs 非终结符:语法树的两类节点
- 手写四则运算解释器(支持变量和括号)
- 扩展:乘法和括号如何零侵入添加?
- JDK 正则 / Spring SpEL 如何体现解释器模式
- 面试必问:解释器 vs 组合模式,有什么区别?
📖 《Java 23 种设计模式:从踩坑到精通》
开篇:系列介绍与目录 | 上一篇:命令模式 | 当前:解释器模式 | 下一篇:迭代器模式
🔗 返回系列总目录
文章目录
1. 从“计算器”的需求说起
想象你要实现一个简单的表达式计算器,能够解析并计算如 a + b - c 这样的表达式。如果采用最直白的字符串分割和 switch 判断:
if (expression.contains("+")) {
// 处理加法
} else if (expression.contains("-")) {
// 处理减法
}
当扩展到乘除、括号、函数调用时,代码会迅速变成一坨难以维护的“意大利面条”。更麻烦的是,如果表达式语法发生变化(如加入幂运算),原有解析逻辑很可能需要重写。
解释器模式(Interpreter Pattern)正是为这类“小语言”解释场景而生:它将每个语法规则表示为一个类,通过这些类的组合构建出抽象语法树,再由客户端输入上下文递归求值。
1.1 你的场景该不该用解释器?
| 判断标准 | 是 → 用解释器 | 否 → 用其他方式 |
|---|---|---|
| 需要自定义简单的语法规则或表达式 | ✅ | ❌ |
| 语法规则可能频繁变化,需要灵活扩展 | ✅ | ❌ |
| 语法结构非常复杂,如完整编程语言 | ❌ | 使用 ANTLR 等专业解析器 |
| 只需要简单的字符串处理或正则 | ❌ | 直接用 String 或 Pattern |
2. 模式定义与 UML 结构
解释器模式 给定一种语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。它属于 行为型设计模式。

图文解析
解释器模式的核心角色:
- 抽象表达式(
Expression):声明一个抽象的解释操作interpret(context),是所有终结符和非终结符表达式的公共父类。 - 终结符表达式(
TerminalExpression):实现与文法中的终结符相关的解释操作,通常是变量或常量,例如a、5。 - 非终结符表达式(
NonTerminalExpression):组合多个表达式,表示语法规则(如加法、减法),内部持有左右两个Expression引用,递归调用子表达式的interpret()完成计算。
核心机制:解释器模式把“解释语言”这件事拆分为一棵由对象组成的语法树,每个节点自己负责自己的求值逻辑。客户端只需构建语法树并调用顶层节点的 interpret(),计算自动递归完成。
3. 代码实现:四则运算表达式解析器
3.1 上下文:变量表
public class Context {
private Map<String, Integer> variables = new HashMap<>();
public void setVariable(String name, int value) {
variables.put(name, value);
}
public int getVariable(String name) {
return variables.getOrDefault(name, 0);
}
}
💬 白话:上下文就是“备忘录”,记录表达式里每个变量代表什么值,比如
a = 10。
3.2 抽象表达式
public interface Expression {
int interpret(Context context);
}
💬 白话:所有表达式节点都必须能“求值”——给定上下文,返回计算结果。
3.3 终结符表达式:变量
public class Variable implements Expression {
private String name;
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
return context.getVariable(name);
}
}
💬 白话:变量节点没有子节点,它直接从上下文中取出对应的数值。
3.4 非终结符表达式:加法、减法
public class Add implements Expression {
private Expression left;
private Expression right;
public Add(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
public class Subtract implements Expression {
private Expression left;
private Expression right;
public Subtract(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
💬 白话:加法/减法节点持有一个左子表达式和一个右子表达式,求值时先递归求出左右两边的值,再执行加减。
3.5 客户端:构建语法树并求值
public class Calculator {
public static void main(String[] args) {
Context ctx = new Context();
ctx.setVariable("a", 10);
ctx.setVariable("b", 5);
ctx.setVariable("c", 2);
// 构建语法树:a + b - c
Expression expression = new Subtract(
new Add(new Variable("a"), new Variable("b")),
new Variable("c")
);
int result = expression.interpret(ctx);
System.out.println("a + b - c = " + result); // 13
}
}
表达式 a + b - c 被构建为一棵语法树:
Subtract
/ \
Add c
/ \
a b
客户端只需创建语法树并调用 interpret(),递归求值自动完成。
4. 扩展:加入乘法和括号
乘法与加法结构相同,括号则需要构建一棵子树。新增运算符无需修改已有类,只需新增一个 NonTerminalExpression 子类。
public class Multiply implements Expression {
private Expression left, right;
public Multiply(Expression left, Expression right) {
this.left = left; this.right = right;
}
public int interpret(Context context) {
return left.interpret(context) * right.interpret(context);
}
}
💬 白话:想增加新运算?加一个新类就好,不用动原来的加法、减法代码。
构建 (a + b) * c:
Expression expr = new Multiply(
new Add(new Variable("a"), new Variable("b")),
new Variable("c")
);
System.out.println(expr.interpret(ctx)); // (10+5)*2 = 30
每加入一个新运算符,只需新增一个 NonTerminalExpression 子类,完全符合开闭原则。
5. 优缺点一览
| 优点 | 缺点 |
|---|---|
| 易于扩展:新增语法规则只需加新表达式类,遵循开闭原则 | 类数量膨胀:每个语法规则一个类,复杂语法会导致类爆炸 |
| 易于实现:语法规则与代码类一一对应,直观 | 执行效率低:递归调用大量对象,解释型求值比原生编译器慢 |
| 可组合:通过组合形成复杂的语法树 | 调试困难:语法树深时调用栈复杂,排查问题成本高 |
| 适合简单语法:特别适合正则、表达式等小语言 | 不适合复杂语法:更推荐使用解析器生成工具(如 ANTLR) |
6. 解释器模式 vs 组合模式
这两种模式都使用树形结构,但目的完全不同:
| 对比维度 | 解释器模式 | 组合模式 |
|---|---|---|
| 目的 | 解释语言句子,求值 | 统一处理部分与整体 |
| 节点行为 | interpret() 计算,不同类型节点行为差异大 |
operation() 统一,叶子与容器行为相似 |
| 典型应用 | 表达式求值、正则、SpEL | 文件系统、组织架构 |
💡 解释器模式的树是“计算树”,每个节点执行不同的计算逻辑;组合模式的树是“结构树”,强调客户一致访问。
7. 框架与实践中的应用
7.1 Java 正则表达式:java.util.regex.Pattern
Java 正则引擎内部将正则表达式字符串解析为一棵语法树,每个元字符(如 *、+、?)对应不同的节点类型,这个过程就是解释器模式的应用。
7.2 Spring Expression Language (SpEL)
Spring 的 SpEL 可以解析并求值复杂的表达式,例如:
ExpressionParser parser = new SpelExpressionParser();
Expression exp = parser.parseExpression("'Hello ' + world");
String value = exp.getValue(context, String.class);
SpEL 内部将表达式字符串解析为 AST,然后递归求值,这正是解释器模式的体现。
7.3 规则引擎(Drools、QLExpress)
规则引擎将业务规则以 DSL 形式编写,内部通过解析器构建 AST,然后注入事实数据执行规则。其核心就是解释器模式。
8. 面试必问 + 面试官追问连环炮
基础必问
- 解释器模式由哪些角色组成? → 抽象表达式、终结符表达式、非终结符表达式、上下文。
- 解释器模式如何遵循开闭原则? → 新增语法规则只需新增表达式类,无需修改原有表达式类。
- 解释器模式的缺点? → 类膨胀、执行效率低,复杂语法不推荐。
面试官追问
- “解释器模式和组合模式有什么区别?”
👉 组合关注“结构统一”,解释器关注“递归求值”。解释器的语法树往往建立在组合模式之上。 - “为什么复杂语法不推荐解释器模式?”
👉 因为每个语法规则都要对应一个类,类数量会爆炸;且递归求值性能远低于直接编译。此时应用 ANTLR 等专业工具。 - “Spring SpEL 是解释器模式吗?”
👉 是。它把表达式字符串解析为 AST,然后递归求值,是解释器模式在企业框架中的典型应用。
🎉 恭喜:如果你能立刻画出
a + b - c的语法树,并说出终结符与非终结符的区别,你已经掌握了行为型模式中“语言即对象树”的设计精髓。
9. 六大设计原则在解释器模式中的体现
| 设计原则 | 在解释器模式中的体现 |
|---|---|
| 单一职责原则(SRP) | 每个表达式类只负责一种语法规则的解释 |
| 开闭原则(OCP) | 新增运算符只需加新类,无需修改现有代码 |
| 里氏替换原则(LSP) | 所有表达式都可替换抽象 Expression |
| 依赖倒置原则(DIP) | 客户端依赖抽象 Expression 构建语法树 |
| 接口隔离原则(ISP) | Expression 接口仅定义 interpret(),精简 |
| 迪米特法则(LoD) | 客户端只与顶级表达式交互,不知内部树结构 |
🧭 《Java 23 种设计模式:从踩坑到精通》快速导航
- 开篇:系列介绍与目录
- 上一篇:命令模式 —— 把操作封装成对象,实现撤销与排队
- 当前:解释器模式 —— 自己动手写一个小语言解释器(你在这里)
- 下一篇:迭代器模式 —— 遍历集合,为什么不直接暴露内部结构?
- 创建型模式汇总:单例、工厂、建造者、原型
- 结构型模式汇总:适配器、装饰器、代理……
- 行为型模式汇总:观察者、策略、模板方法……
🔔 关注《Java 23 种设计模式:从踩坑到精通》,用 25 篇文章彻底吃透设计模式。
📦 福利预告:全系列代码及 UML 源码将在完结时统一打包开放,点击「关注」「收藏」第一时间获取。
🚀 下一篇:迭代器模式:遍历集合,为什么不直接暴露内部结构?
📌 除了设计模式,我也在深挖智能物流实战(WMS、托盘调度、机器学习落地)。欢迎点击头像,看看专栏 《出版社物流WMS智能调度实战》。技术相通,思路可鉴。
更多推荐

所有评论(0)