设计模式系列文章(基础篇第41篇):解释器模式——自定义语法规则,实现表达式解析

大家好,欢迎来到设计模式系列文章(基础篇)的第四十一篇内容,同时也是行为型设计模式的终篇。在上一篇中,我们彻底吃透了命令模式,核心是将行为请求封装为对象,实现调用方与执行方的解耦,灵活支持任务排队、撤销重做、批量操作等业务能力,是业务系统中落地极广的行为型模式。

今天我们学习整个基础系列中最特殊、最小众、最偏向底层原理的行为型模式------解释器模式。相比于命令模式、备忘录模式等高频业务模式,解释器模式极少用于普通业务开发,但却是框架底层、规则引擎、表达式解析、脚本解析的核心基石。

日常开发中,我们经常遇到"自定义规则解析"的场景:四则运算表达式计算、自定义权限表达式、风控规则解析、配置文件表达式、公式计算引擎、简单脚本执行。这类场景的共性是:有一套固定的自定义语法,需要程序自动解析语法、执行逻辑、输出结果。而解释器模式,正是专门解决"自定义语言/表达式解析"的专属设计模式。

本文将从零拆解解释器模式的核心思想、角色结构、执行流程,通过完整的四则运算表达式实战案例,带你彻底掌握该模式的底层逻辑、适用场景与落地边界,同时讲清它的优缺点与替代方案,解决大家"学了不会用、不知何时用"的痛点。

一、解释器模式核心定义与设计初衷

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() → 终结符返回基础值 → 非终结符逐层计算合并 → 输出最终结果。

三、解释器模式标准执行流程

  1. 定义抽象表达式接口,统一声明解释执行方法;
  2. 创建终结符表达式,实现最小语法单元的解析逻辑;
  3. 创建各类非终结符表达式,实现嵌套递归解析逻辑;
  4. 定义上下文环境,存储解析所需全局数据;
  5. 客户端解析原始字符串,拆分语法单元,组装成树形语法结构;
  6. 递归执行所有表达式的解释方法,逐层计算得到最终结果。

四、解释器模式实战案例:四则运算表达式解析

我们采用最经典、最易理解的自定义四则运算表达式解析完成完整实战。场景需求:自定义解析加减乘除嵌套表达式(如 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等主流框架的源码落地,以及业务复杂场景的模式组合实战、架构设计选型、模式避坑实战,从"会用模式"进阶到"活用模式、架构级落地"。

感谢大家一路陪伴学习,进阶篇章,我们再会!