【设计模式】【行为型模式】解释器模式(Interpreter)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 什么是解释器模式?
- 为什么要解释器模式?
- 如何实现解释器模式?
- 二、解释器模式在框架源中的运用
- Spring 表达式语言(SpEL)
- 三、总结
- 解释器模式的优点
- 解释器模式的缺点
- 解释器模式的适用场景
一、入门
什么是解释器模式?
解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义语言的语法表示,并提供一个解释器来处理该语法。它通常用于需要解释和执行特定语言或表达式的场景。
为什么要解释器模式?
假设一个电商平台需要实现动态的促销规则,例如:
- 规则1:用户是 VIP 且 订单金额 ≥ 100 元 → 享受 20 元优惠。
- 规则2:商品类别是 “电子产品” 或 库存量 > 50 → 允许参加秒杀活动。
这些规则需要灵活配置,并且随着业务发展可能会新增条件(例如添加“用户年龄 ≤ 30 岁”等)。
下面是没有用解释器模式的实现代码。
public class PromotionRuleWithoutInterpreter {public static boolean checkRule1(Map<String, Object> context) {boolean isVip = (boolean) context.get("isVip");double orderAmount = (double) context.get("orderAmount");return isVip && orderAmount >= 100;}public static boolean checkRule2(Map<String, Object> context) {String category = (String) context.get("productCategory");int stock = (int) context.get("stock");return category.equals("electronics") || stock > 50;}// 每新增一个规则,都需要添加一个新方法,且逻辑无法复用!
}
存在问题
- 重复代码:每个规则都需要手动解析字段和逻辑。
- 难以扩展:新增规则需要修改代码,违反开闭原则。
- 维护困难:如果字段名或条件逻辑变化,需要修改所有相关方法。
如何实现解释器模式?
解释器模式的构成:
- 抽象表达式(Abstract Expression):定义解释操作的接口,通常包含一个
interpret()方法。 - 终结符表达式(Terminal Expression):实现与语法中的终结符相关的解释操作。
- 非终结符表达式(Non-terminal Expression):实现语法中的规则,通常包含对其他表达式的引用。
- 上下文(Context):包含解释器需要的全局信息。
- 客户端(Client):构建语法树并调用解释操作。主要讲需要分析的句子或表达式转换成解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环节角色间访问解释器的解释方法。
【案例】大促规则 - 改

抽象表达式(Abstract Expression):RuleExpression接口。
interface RuleExpression {boolean interpret(Map<String, Object> context);
}
终结符表达式(Terminal Expression): IsVipExpressio类,判断用户是否为vip;OrderAmountExpression判断订单金额是否≥指定值;ProductCategoryExpression类,判断商品类别是否匹配。
// 终结符表达式:用户是否是VIP
class IsVipExpression implements RuleExpression {@Overridepublic boolean interpret(Map<String, Object> context) {return (boolean) context.get("isVip");}
}// 终结符表达式:订单金额是否≥指定值
class OrderAmountExpression implements RuleExpression {private double minAmount;public OrderAmountExpression(double minAmount) {this.minAmount = minAmount;}@Overridepublic boolean interpret(Map<String, Object> context) {double amount = (double) context.get("orderAmount");return amount >= minAmount;}
}// 终结符表达式:商品类别是否匹配
class ProductCategoryExpression implements RuleExpression {private String category;public ProductCategoryExpression(String category) {this.category = category;}@Overridepublic boolean interpret(Map<String, Object> context) {String actualCategory = (String) context.get("productCategory");return actualCategory.equals(category);}
}
非终结符表达式(组合条件):AndExpression类,逻辑"与"操作。OrExpression类,逻辑"或"操作。
// 非终结符表达式:逻辑"与"操作
class AndExpression implements RuleExpression {private RuleExpression expr1;private RuleExpression expr2;public AndExpression(RuleExpression expr1, RuleExpression expr2) {this.expr1 = expr1;this.expr2 = expr2;}@Overridepublic boolean interpret(Map<String, Object> context) {return expr1.interpret(context) && expr2.interpret(context);}
}// 非终结符表达式:逻辑"或"操作
class OrExpression implements RuleExpression {private RuleExpression expr1;private RuleExpression expr2;public OrExpression(RuleExpression expr1, RuleExpression expr2) {this.expr1 = expr1;this.expr2 = expr2;}@Overridepublic boolean interpret(Map<String, Object> context) {return expr1.interpret(context) || expr2.interpret(context);}
}
客户端,假设需要判断用户是否满足,规则1(VIP 且订单金额≥100元),客户端是负责 构建规则表达式 并 调用解释器执行规则 的部分。
public class PromotionRuleDemo {public static void main(String[] args) {// 上下文数据(模拟用户订单信息)Map<String, Object> context = new HashMap<>();context.put("isVip", true);context.put("orderAmount", 150.0);// 构建规则表达式:isVip && orderAmount >= 100RuleExpression rule1 = new AndExpression(new IsVipExpression(),new OrderAmountExpression(100.0));// 解释并执行规则boolean canApplyDiscount = rule1.interpret(context);System.out.println("是否满足促销规则1: " + canApplyDiscount); // 输出: true}
}
改造后的好处:
- 规则可配置化:
可以将促销规则抽象为表达式对象,甚至通过配置文件或数据库动态生成规则树,无需修改代码。
例如:将规则(isVip && orderAmount >= 100) || (productCategory == "electronics")存储为 JSON,动态解析为表达式对象。 - 灵活组合条件:
通过组合AndExpression、OrExpression,可以轻松实现复杂的逻辑。 - 易于扩展:
新增条件(例如“用户年龄 ≤ 30”)只需添加新的终结符表达式,无需改动现有代码。
二、解释器模式在框架源中的运用
Spring 表达式语言(SpEL)
Spring 的 SpEL(Spring Expression Language)允许在运行时解析字符串表达式(如 "user.name" 或 "price * quantity"),并绑定到对象属性或执行逻辑。其底层实现使用了解释器模式的思想。
下面的代码时SpEL在 @Value 注解中注入动态值
@Component
public class AppConfig {// 注入配置文件中的值@Value("${app.name}")private String appName;// 使用 SpEL 计算值@Value("#{ T(java.lang.Math).random() * 100.0 }")private double randomNumber;// 引用其他 Bean 的属性@Value("#{userService.defaultUser.name}")private String defaultUserName;
}
SpEL 的核心流程分为两个阶段:
- 解析阶段:将字符串表达式(如 “2 + 3 * 4”)解析为 抽象语法树(AST),树中的每个节点对应一个表达式对象。
- 执行阶段:递归遍历 AST,解释每个节点并计算结果。这一过程完美契合解释器模式的 语法树解释执行 思想。
OpPlus/ \"2" OpMultiply/ \"3" "4"
下面是对源码的分析:
抽象表达式:Expression 接口,所有具体表达式(如字面量、运算符、方法调用)都实现此接口。
public interface Expression {// 核心方法:解释表达式并返回结果Object getValue() throws EvaluationException;// 其他重载方法(支持上下文、目标类型等)
}
终结符表达式示例:LiteralExpression,LiteralExpression 直接解析字面量(如 “100”),无需依赖其他表达式。
public class LiteralExpression implements Expression {private final String literalValue;public LiteralExpression(String literalValue) {this.literalValue = literalValue;}@Overridepublic Object getValue() {// 直接返回字面量值(如 "42" 转换为整数)return this.literalValue;}
}
非终结符表达式示例:OpPlus(加法操作)。OpPlus 组合了左、右两个操作数(可能是其他表达式对象),递归解释执行。
public class OpPlus extends Operator {@Overridepublic TypedValue getValueInternal(ExpressionState state) throws EvaluationException {// 递归获取左、右操作数的值Object leftOperand = getLeftOperand().getValueInternal(state).getValue();Object rightOperand = getRightOperand().getValueInternal(state).getValue();// 执行加法操作return new TypedValue(leftOperand + rightOperand);}
}
上下文:EvaluationContext接口,StandardEvaluationContext 是默认实现,提供变量绑定和类型支持。
public interface EvaluationContext {// 获取变量值(如 "#user")Object lookupVariable(String name);// 获取类型转换器、函数等TypeConverter getTypeConverter();
}
客户端:SpelExpressionParser:SpelExpressionParser 负责将字符串转换为 Expression 对象(语法树的根节点)。
public class SpelExpressionParser {// 解析字符串为 Expression 对象(语法树)public Expression parseExpression(String expressionString) {// 使用 Tokenizer 分词,Parser 构建 ASTreturn this.doParseExpression(expressionString);}
}
测试类
public class SpELAdditionExample {public static void main(String[] args) {// 1. 创建 SpEL 解析器ExpressionParser parser = new SpelExpressionParser();// 2. 解析加法表达式Expression expr = parser.parseExpression("2 + 3 * 4");// 3. 执行表达式并获取结果Integer result = expr.getValue(Integer.class);// 4. 输出结果System.out.println("计算结果: " + result); // 输出: 计算结果: 14}
}
三、总结
解释器模式的优点
- 易于扩展语法规则
通过添加新的表达式类,可以轻松扩展语法规则,符合 开闭原则(对扩展开放,对修改封闭)。 - 实现简单语法解析
对于简单的语法规则,解释器模式提供了一种直观的实现方式,将语法规则分解为多个表达式类。 - 解耦语法解析与执行
将语法解析逻辑封装在表达式类中,与业务逻辑解耦,使代码更清晰、更易维护。 - 适合领域特定语言(DSL)
解释器模式非常适合实现 领域特定语言(如规则引擎、查询语言等),能够将复杂的业务规则抽象为表达式树。 - 灵活性
可以通过组合不同的表达式类,动态构建复杂的语法树,支持运行时修改规则。
解释器模式的缺点
- 复杂性高
对于复杂的语法规则,解释器模式会导致类的数量急剧增加(每个规则都需要一个表达式类),增加系统复杂性。 - 性能问题
解释器模式通常采用递归解释执行,性能较低,不适合对性能要求较高的场景。 - 难以维护
随着语法规则的增加,表达式类的数量会变得庞大,导致代码难以维护。 - 不适合复杂语法
解释器模式更适合处理简单的语法规则,对于复杂的语法(如编程语言),使用解释器模式会变得非常笨拙。 - 学习成本高
需要开发者熟悉语法树的设计和递归解释执行的原理,增加了学习和实现的难度。
解释器模式的适用场景
- 领域特定语言(DSL)
当需要实现一种简单的领域特定语言时,解释器模式是一种自然的选择。例如:- 规则引擎(如促销规则、风控规则)。
- 查询语言(如 SQL 条件解析)。
- 模板引擎(如动态生成邮件内容)。
- 需要动态解析和执行规则的场景
当规则需要动态配置(如从数据库或配置文件中加载)并在运行时解析执行时,解释器模式非常适用。 - 语法规则相对固定且简单
如果语法规则不会频繁变化,且规则数量较少,解释器模式可以很好地满足需求。 - 不适合使用编译器或解析器生成工具的场景
对于简单的语法规则,使用编译器或解析器生成工具(如 ANTLR)可能过于复杂,解释器模式提供了一种轻量级的解决方案。
相关文章:
【设计模式】【行为型模式】解释器模式(Interpreter)
👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD 🔥 2025本人正在沉淀中… 博客更新速度 👍 欢迎点赞、收藏、关注,跟上我的更新节奏 🎵 当你的天空突…...
修改OnlyOffice编辑器默认字体
通过Docker修改OnlyOffice编辑器默认字体 问题描述详细方案1. 删除原生字体文件2. 创建字体目录3. 复制字体文件到容器中4. 执行字体更新脚本5. 重新启动容器 注意事项 问题描述 在OnlyOffice中,编辑器的默认字体可能不符合公司或个人的需求,通常会使用…...
React echarts柱状图点击某个柱子跳转页面
绘制echarts柱状图 在 ECharts 中,如果你想要在点击柱状图的某个柱子时进行页面跳转,你可以通过设置 series 中的 data 属性中的 itemStyle 或者使用 series 的 label 属性中的 emphasis 属性来实现。但是,直接在柱状图中实现点击跳转通常涉…...
wordpress主题插件开发中高频使用的38个函数
核心模板函数 get_header()/get_footer()/get_sidebar() – 加载模板部件 the_title()/the_content()/the_excerpt() – 显示文章标题、内容、摘要 the_post() – 循环中获取文章数据 bloginfo(‘url’) – 获取站点URL wp_head()/wp_footer() – 输出头部/尾部代码 wp_n…...
ElasticSearch基础和使用
ElasticSearch基础 1 初识ES相关组件 (1)Elasticsearch是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack(ELK) 广泛应…...
qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene
qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene code review! 文章目录 qt-C++笔记之QGraphicsScene和 QGraphicsView中setScene、通过scene得到view、通过view得scene1.`setScene` 方法2.通过 `scene` 获取它的视图 (`views()`)…...
小白win10安装并配置yt-dlp
需要yt-dlp和ffmpeg 注意存放路径最好都是全英文 win10安装并配置yt-dlp 一、下载1.下载yt-dlp2. fffmpeg下载 二、配置环境三、cmd操作四、yt-dlp下视频操作 一、下载 1.下载yt-dlp yt-dlp地址 找到win的压缩包点下载,并解压 2. fffmpeg下载 ffmpeg官方下载 …...
【kafka系列】broker
目录 Broker 接收生产者消息和返回消息给消费者的流程逻辑分析 Broker 处理生产者消息的核心流程 Broker 处理消费者消息的核心流程 关键点总结 Broker 接收生产者消息和返回消息给消费者的流程逻辑分析 Broker 处理生产者消息的核心流程 接收请求 Broker 的 SocketServer …...
用大模型学大模型05-线性回归
deepseek.com:多元线性回归的目标函数,损失函数,梯度下降 标量和矩阵形式的数学推导,pytorch真实能跑的代码案例以及模型,数据,预测结果的可视化展示, 模型应用场景和优缺点,及如何改进解决及改进方法数据推…...
Python实现AWS Fargate自动化部署系统
一、背景介绍 在现代云原生应用开发中,自动化部署是提高开发效率和保证部署质量的关键。AWS Fargate作为一项无服务器计算引擎,可以让我们专注于应用程序开发而无需管理底层基础设施。本文将详细介绍如何使用Python实现AWS Fargate的完整自动化部署流程。 © ivwdcwso (ID…...
国产编辑器EverEdit - 上下翻滚不迷路(历史编辑位置、历史光标位置回溯功能)
1 光标位置跳转 1.1 应用场景 某些场景下,用户从当前编辑位置跳转到别的位置查阅信息,如果要快速跳转回之前编辑位置,则可以使用光标跳转相关功能。 1.2 使用方法 1.2.1 上一个编辑位置 跳转到上一个编辑位置,即文本修改过的位…...
今日写题work05
题目:用队列实现栈 思路 队列的特点是先进先出,而栈的特点是后进先出。所以想要用队列实现模拟栈,我们可以使用两个队列,一个队列负责压栈,一个队列负责出栈。压栈很简单就是检空再调用队列的push就好,那出…...
[C++语法基础与基本概念] std::function与可调用对象
std::function与可调用对象 函数指针lambda表达式std::function与std::bind仿函数总结std::thread与可调用对象std::async与可调用对象回调函数 可调用对象是指那些像函数一样可以直接被调用的对象,他们广泛用于C的算法,回调,事件处理等机制。…...
两个实用且热门的 Python 爬虫案例,结合动态/静态网页抓取和反爬策略,附带详细代码和实现说明
在这个瞬息万变的世界里,保持一颗探索的心,永远怀揣梦想前行。即使有时会迷失方向,也不要忘记内心深处那盏指引你前进的明灯。它代表着你的希望、你的信念以及对未来的无限憧憬。每一个不曾起舞的日子,都是对生命的辜负࿱…...
华象新闻 | 2月20日前谨慎升级 PostgreSQL 版本
各位 PostgreSQL 用户,建议近期进行升级 PostgreSQL 版本。 2月20日计划进行非周期性版本发布 PostgreSQL全球开发团队计划于2025年2月20日进行一次非周期性发布,以解决2025年2月13日更新版本中引入的一个回归问题。 2月13日的更新版本包括了17.3、16.7、…...
跳跃游戏 II - 贪心算法解法
问题描述: 给定一个长度为 n 的 0 索引整数数组 nums,我们从数组的第一个元素 nums[0] 开始。每个元素 nums[i] 表示从索引 i 可以跳跃的最大长度,换句话说,从位置 i,你可以跳到位置 i j,其中 0 < j &…...
图像质量评价指标-UCIQE-UIQM
一、评价指标UCIQE 在文章《An underwater color image quality evaluation metric》中,提到的了评价指标UCIQE(Underwater Colour Image Quality Evaluation),是一种无参考图像质量评价指标,主要用于评估水下图像的质…...
CentOS上安装WordPress
在CentOS上安装WordPress是一个相对直接的过程,可以通过多种方法完成,包括使用LAMP(Linux, Apache, MySQL, PHP)栈或使用更现代的LEMP(Linux, Nginx, MySQL, PHP)栈。 我选择的是(Linux, Nginx…...
Spring Boot 原理分析
spring-boot.version:2.4.3.RELEASE Spring Boot 依赖管理 spring-boot-starter-parent 配置文件管理 <resources> <resource> <directory>${basedir}/src/main/resources</directory> <filtering>true&l…...
Git 本地项目上传 GitHub 全指南(SSH Token 两种上传方式详细讲解)
前言:Git 与 GitHub 的区别与联系 在学习如何将本地项目上传到 GitHub 之前,先来弄清楚 Git 和 GitHub 的区别以及它们之间的联系。 对比项GitGitHub定义分布式版本控制系统(DVCS),用于本地和远程管理代码版本托管 G…...
边缘计算医疗风险自查APP开发方案
核心目标:在便携设备(智能手表/家用检测仪)部署轻量化疾病预测模型,实现低延迟、隐私安全的实时健康风险评估。 一、技术架构设计 #mermaid-svg-iuNaeeLK2YoFKfao {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg…...
MFC内存泄露
1、泄露代码示例 void X::SetApplicationBtn() {CMFCRibbonApplicationButton* pBtn GetApplicationButton();// 获取 Ribbon Bar 指针// 创建自定义按钮CCustomRibbonAppButton* pCustomButton new CCustomRibbonAppButton();pCustomButton->SetImage(IDB_BITMAP_Jdp26)…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
【android bluetooth 框架分析 04】【bt-framework 层详解 1】【BluetoothProperties介绍】
1. BluetoothProperties介绍 libsysprop/srcs/android/sysprop/BluetoothProperties.sysprop BluetoothProperties.sysprop 是 Android AOSP 中的一种 系统属性定义文件(System Property Definition File),用于声明和管理 Bluetooth 模块相…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
RNN避坑指南:从数学推导到LSTM/GRU工业级部署实战流程
本文较长,建议点赞收藏,以免遗失。更多AI大模型应用开发学习视频及资料,尽在聚客AI学院。 本文全面剖析RNN核心原理,深入讲解梯度消失/爆炸问题,并通过LSTM/GRU结构实现解决方案,提供时间序列预测和文本生成…...
大数据学习(132)-HIve数据分析
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
基于TurtleBot3在Gazebo地图实现机器人远程控制
1. TurtleBot3环境配置 # 下载TurtleBot3核心包 mkdir -p ~/catkin_ws/src cd ~/catkin_ws/src git clone -b noetic-devel https://github.com/ROBOTIS-GIT/turtlebot3.git git clone -b noetic https://github.com/ROBOTIS-GIT/turtlebot3_msgs.git git clone -b noetic-dev…...
