大家好,欢迎来到设计模式系列文章(基础篇)的第四十一篇内容,同时也是行为型设计模式的终篇。在上一篇中,我们彻底吃透了命令模式,核心是将行为请求封装为对象,实现调用方与执行方的解耦,灵活支持任务排队、撤销重做、批量操作等业务能力,是业务系统中落地极广的行为型模式。
今天我们学习整个基础系列中最特殊、最小众、最偏向底层原理的行为型模式------解释器模式。相比于命令模式、备忘录模式等高频业务模式,解释器模式极少用于普通业务开发,但却是框架底层、规则引擎、表达式解析、脚本解析的核心基石。
日常开发中,我们经常遇到"自定义规则解析"的场景:四则运算表达式计算、自定义权限表达式、风控规则解析、配置文件表达式、公式计算引擎、简单脚本执行。这类场景的共性是:有一套固定的自定义语法,需要程序自动解析语法、执行逻辑、输出结果。而解释器模式,正是专门解决"自定义语言/表达式解析"的专属设计模式。
本文将从零拆解解释器模式的核心思想、角色结构、执行流程,通过完整的四则运算表达式实战案例,带你彻底掌握该模式的底层逻辑、适用场景与落地边界,同时讲清它的优缺点与替代方案,解决大家"学了不会用、不知何时用"的痛点。
一、解释器模式核心定义与设计初衷
1. 核心定义
解释器模式(Interpreter Pattern):给定一门语言,定义它的文法(语法)表示,并建立一个解释器来解释该语言中的句子,用于解决固定语法、可递归拆解的表达式解析问题。
一句话核心概括:定义语法规则,将复杂表达式拆解为最小单元,通过递归解释执行,实现自定义表达式自动计算。
通俗理解:解释器模式就像"翻译官"。我们自定义一套语法规则(比如加减乘除表达式、权限规则表达式),程序按照这套语法,把人类书写的表达式,逐字拆解、翻译、计算,最终输出执行结果。所有复杂表达式,都可以递归拆分为简单的子表达式,逐个解释执行,最终合并结果。
2. 解决的核心痛点
在没有解释器模式的情况下,解析自定义表达式、语法规则,通常只能硬编码if/else、嵌套判断,会出现严重问题:
- 语法扩展极差:新增语法、新增运算符、新增规则,需要大面积修改解析代码,违背开闭原则;
- 代码嵌套臃肿:复杂多层嵌套表达式,if/else嵌套层级极深,代码可读性、可维护性极差;
- 无法递归解析:对于嵌套表达式、递归规则,硬编码难以通用处理,只能针对性写死逻辑;
- 规则与代码耦合 :语法规则硬编码在业务中,无法动态配置、动态修改,灵活性极低。
解释器模式完美解决以上问题:将每一种语法规则、每一种表达式单元独立封装,通过递归机制通用解析嵌套表达式,实现语法规则的解耦与可扩展。
3. 设计原则适配
- 单一职责原则:每个具体表达式类只负责解析一种语法单元(数字、加法、减法、乘法),职责单一清晰;
- 开闭原则:新增运算符、新增语法规则,只需新增表达式实现类,无需修改原有解析代码;
- 依赖倒置原则:所有解析逻辑依赖抽象表达式接口,不依赖具体语法实现类;
- 递归拆解思想:天然适配树形、嵌套式表达式结构,层层拆解、逐层解析。
二、解释器模式五大核心角色
解释器模式结构固定,包含五大核心角色,所有表达式解析场景均遵循该结构,我们以四则运算表达式解析场景拆解角色职责:
1. 抽象表达式(Abstract Expression)
所有表达式的顶层抽象接口,定义统一的解释执行方法。所有语法单元(数字、运算符号)都需要实现该接口,通用规范所有解析逻辑。核心方法为 interpret() 解释方法,用于执行表达式并返回结果。
2. 终结符表达式(Terminal Expression)
最小的、不可拆分的语法单元,是表达式解析的最小颗粒度,无法继续递归拆解。在四则运算中,数字就是终结符表达式,是整个表达式的基础单元,负责返回自身数值。一个语法体系中,终结符通常数量最少。
3. 非终结符表达式(Non-terminal Expression)
可拆分、可嵌套的语法单元,通常是运算规则、逻辑规则。内部包含多个子表达式,会递归调用子表达式的interpret方法完成解析。在四则运算中,加法、减法、乘法、除法都是非终结符表达式,每个运算都包含左右两个子表达式,可无限嵌套。
4. 上下文环境(Context)
存储全局解析信息、变量数据、临时状态的容器,用于统一传递解析上下文。比如存储表达式中的变量值、解析过程的缓存数据、语法配置信息。简单场景可省略,复杂规则解析场景必不可少。
5. 客户端(Client)
负责将原始字符串表达式,拆解、构建为抽象表达式语法树,最终调用根节点的解释方法,完成整个表达式的解析与计算。核心职责:语法拆分、构建语法树、触发解析执行。
核心执行链路总结:客户端拆分表达式 → 构建树形表达式结构 → 从根节点递归调用interpret() → 终结符返回基础值 → 非终结符逐层计算合并 → 输出最终结果。
三、解释器模式标准执行流程
- 定义抽象表达式接口,统一声明解释执行方法;
- 创建终结符表达式,实现最小语法单元的解析逻辑;
- 创建各类非终结符表达式,实现嵌套递归解析逻辑;
- 定义上下文环境,存储解析所需全局数据;
- 客户端解析原始字符串,拆分语法单元,组装成树形语法结构;
- 递归执行所有表达式的解释方法,逐层计算得到最终结果。
四、解释器模式实战案例:四则运算表达式解析
我们采用最经典、最易理解的自定义四则运算表达式解析完成完整实战。场景需求:自定义解析加减乘除嵌套表达式(如 10 + (20 - 5) * 2),不依赖JDK自带计算公式,纯手动通过解释器模式实现表达式拆解、递归计算,完美还原解释器的核心递归解析思想。
1. 抽象表达式接口
bash
// 抽象表达式:所有表达式统一接口
public interface Expression {
// 解释执行方法,返回表达式计算结果
int interpret();
}
2. 终结符表达式:数字表达式
数字是最小语法单元,不可拆分,直接返回自身数值。
bash
// 终结符表达式:数字表达式
public class NumberExpression implements Expression {
// 存储数字数值
private int value;
public NumberExpression(int value) {
this.value = value;
}
// 直接返回当前数字,无需递归
@Override
public int interpret() {
return this.value;
}
}
3. 非终结符表达式:加减乘除运算
所有运算表达式均包含左右两个子表达式,支持嵌套递归计算。
bash
// 加法非终结符表达式
public class AddExpression implements Expression {
// 左右子表达式,支持任意嵌套表达式
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
// 递归计算左右表达式结果后相加
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
// 减法非终结符表达式
public class SubExpression implements Expression {
private Expression left;
private Expression right;
public SubExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
// 乘法非终结符表达式
public class MulExpression implements Expression {
private Expression left;
private Expression right;
public MulExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
// 除法非终结符表达式
public class DivExpression implements Expression {
private Expression left;
private Expression right;
public DivExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() / right.interpret();
}
}
4. 客户端测试:组装语法树、解析嵌套表达式
测试嵌套表达式:10 + (20 - 5) * 2,手动拆解语法树,递归计算结果。
bash
public class InterpreterTest {
public static void main(String[] args) {
// 表达式:10 + (20 - 5) * 2
// 分步拆解构建语法树
// 1. 终结符:数字10、20、5、2
Expression num10 = new NumberExpression(10);
Expression num20 = new NumberExpression(20);
Expression num5 = new NumberExpression(5);
Expression num2 = new NumberExpression(2);
// 2. 子表达式:20 - 5
Expression sub = new SubExpression(num20, num5);
// 3. 子表达式:(20-5) * 2
Expression mul = new MulExpression(sub, num2);
// 4. 最终表达式:10 + ((20-5)*2)
Expression finalExp = new AddExpression(num10, mul);
// 递归解释执行,输出结果
System.out.println("表达式 10 + (20 - 5) * 2 计算结果:" + finalExp.interpret());
}
}
运行结果
bash
表达式 10 + (20 - 5) * 2 计算结果:40
结果完全正确,整个执行过程完美体现解释器核心思想:复杂表达式拆分为多层子表达式,通过递归逐层解释计算,最终合并结果。新增取模、平方、括号优先级等语法时,只需新增对应非终结符表达式类,无需改动原有代码,扩展性极强。
五、带上下文的高级实战:变量表达式解析
真实业务中,表达式往往包含变量(如 a + b * c),需要上下文存储变量值。我们升级案例,实现带变量的表达式解析,演示Context上下文的实际用途。
1. 上下文环境类
bash
import java.util.HashMap;
import java.util.Map;
// 上下文:存储变量与对应数值
public class Context {
// 存储变量 key:变量名 value:变量值
private Map<String, Integer> varMap = new HashMap<>();
// 设置变量
public void setValue(String key, Integer value) {
varMap.put(key, value);
}
// 获取变量值
public int getValue(String key) {
return varMap.get(key);
}
}
2. 变量终结符表达式
bash
// 变量终结符表达式
public class VarExpression implements Expression {
private String key;
public VarExpression(String key) {
this.key = key;
}
// 从上下文获取变量值
@Override
public int interpret(Context context) {
return context.getValue(key);
}
}
3. 改造运算表达式支持上下文
bash
// 加法表达式(支持变量)
public class AddVarExpression implements Expression {
private Expression left;
private Expression right;
public AddVarExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
// 减法、乘法、除法同理,仅方法参数增加Context上下文
4. 变量表达式测试
bash
public class VarInterpreterTest {
public static void main(String[] args) {
// 表达式:a + b * c
Context context = new Context();
context.setValue("a", 10);
context.setValue("b", 20);
context.setValue("c", 5);
// 构建语法树
Expression a = new VarExpression("a");
Expression b = new VarExpression("b");
Expression c = new VarExpression("c");
Expression mul = new MulVarExpression(b, c);
Expression finalExp = new AddVarExpression(a, mul);
// 计算结果 10 + 20*5 = 110
System.out.println("变量表达式 a + b * c 计算结果:" + finalExp.interpret(context));
}
}
该案例更贴近真实业务,通过上下文统一管理变量数据,实现了表达式与数据的解耦,修改变量值无需修改表达式语法树,灵活性大幅提升。
六、解释器模式高频落地场景
解释器模式不适合普通CRUD业务,仅适用于「有固定自定义语法、可递归拆解、需要动态解析执行」的场景,核心落地场景如下:
1. 表达式计算引擎
四则运算、公式计算、自定义数值公式解析,如报表公式计算、薪资计算公式、业绩计算公式、数学表达式求值。
2. 规则引擎与风控系统
自定义业务规则解析,如风控规则(用户等级 > 80 且 消费金额 > 1000)、权限规则、审批条件、过滤条件表达式解析。
3. 配置文件与动态脚本解析
自定义配置语法、动态配置表达式、简单脚本解析,如低代码平台的公式配置、动态条件配置、可视化规则配置。
4. 框架底层经典应用
- MyBatis:#{}、${} 表达式解析、动态SQL条件解析,底层基于解释器思想;
- Spring EL表达式:SpringExpression语言解析,用于注解、配置、动态取值;
- 正则表达式:正则语法解析匹配,本质是解释器模式的落地;
- 各类脚本引擎:Lua、Groovy轻量脚本解析核心原理均为解释器模式。
七、解释器模式优缺点与避坑指南
1. 核心优点
- 语法扩展灵活:新增运算符、语法规则、逻辑表达式,只需新增实现类,符合开闭原则;
- 代码逻辑清晰:不同语法单元独立封装,彻底拆分复杂嵌套逻辑,告别臃肿if/else;
- 天然支持递归解析:完美适配多层嵌套、递归表达式,通用解析能力极强;
- 规则与业务解耦:语法规则独立维护,支持动态配置、动态修改,不侵入业务代码。
2. 核心缺点
- 类数量爆炸:每一种语法、运算符都需要单独创建类,复杂语法体系会产生大量子类;
- 递归性能较差:多层嵌套表达式递归解析,会产生大量栈调用,复杂场景存在性能损耗;
- 学习成本高:递归思想、语法树构建抽象度高,相比于普通设计模式更难理解和维护;
- 场景极度受限:仅适用于固定语法的表达式解析,绝大多数业务场景无需使用。
3. 避坑指南(核心重点)
- 普通业务绝对不用:简单判断、固定逻辑、无需动态解析的场景,禁止强行使用,避免过度设计;
- 复杂语法优先第三方框架:正式开发中,复杂表达式、脚本解析优先使用Spring EL、Groovy、QLExpress,不手写解释器;
- 控制递归层级:自定义解释器时,需要做递归深度限制,防止超大嵌套表达式导致栈溢出;
- 区分使用场景:只有需要自定义全新语法、动态解析规则时,才考虑手写简易解释器。
八、行为型模式整体总结
至此,我们全部23种GoF设计模式基础篇正式完结!其中行为型模式共计11种,是种类最多、业务落地最丰富的一类模式,这里做全局极简总结,帮大家梳理体系:
- 责任链模式:链式传递请求,实现请求处理解耦,适用于多级校验、拦截处理;
- 观察者模式:发布订阅解耦,适用于事件通知、消息监听、数据联动更新;
- 策略模式:封装算法,动态替换,解决大量if/else算法分支;
- 模板方法模式:固定流程骨架,开放扩展点,统一流程、差异化实现;
- 迭代器模式:统一遍历接口,屏蔽集合底层细节;
- 状态模式:封装状态行为,解决状态切换的复杂分支逻辑;
- 中介者模式:解耦多对象复杂交互,统一中转通信;
- 访问者模式:分离数据结构与操作,实现操作逻辑灵活扩展;
- 备忘录模式:保存对象状态,实现撤销、回滚、快照恢复;
- 命令模式:封装请求行为,解耦调用与执行,支持排队、撤销、批量操作;
- 解释器模式:自定义语法规则,递归解析表达式、自定义脚本。
九、系列专栏完结预告
本篇解释器模式的更新,标志着《设计模式基础系列23种GoF模式》全篇完结。从创建型、结构型到行为型,我们逐一拆解每种模式的定义、初衷、结构、源码实战、场景落地、优缺点与避坑指南,从零到一带大家完整掌握设计模式体系。
后续专栏将开启进阶专题篇章:重点讲解设计模式在Spring、MyBatis、Dubbo、Redis等主流框架的源码落地,以及业务复杂场景的模式组合实战、架构设计选型、模式避坑实战,从"会用模式"进阶到"活用模式、架构级落地"。
感谢大家一路陪伴学习,进阶篇章,我们再会!