当前位置: 首页 > news >正文

【设计模式】【行为型模式】解释器模式(Interpreter)

👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中… 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云

文章目录

  • 一、入门
    • 什么是解释器模式?
    • 为什么要解释器模式?
    • 如何实现解释器模式?
  • 二、解释器模式在框架源中的运用
    • Spring 表达式语言(SpEL)
  • 三、总结
    • 解释器模式的优点
    • 解释器模式的缺点
    • 解释器模式的适用场景

一、入门

什么是解释器模式?

解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义语言的语法表示,并提供一个解释器来处理该语法。它通常用于需要解释和执行特定语言或表达式的场景。

为什么要解释器模式?

假设一个电商平台需要实现动态的促销规则,例如:

  1. 规则1:用户是 VIP 且 订单金额 ≥ 100 元 → 享受 20 元优惠。
  2. 规则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;}// 每新增一个规则,都需要添加一个新方法,且逻辑无法复用!
}

存在问题

  1. 重复代码:每个规则都需要手动解析字段和逻辑。
  2. 难以扩展:新增规则需要修改代码,违反开闭原则。
  3. 维护困难:如果字段名或条件逻辑变化,需要修改所有相关方法。

如何实现解释器模式?

解释器模式的构成

  1. 抽象表达式(Abstract Expression):定义解释操作的接口,通常包含一个interpret()方法。
  2. 终结符表达式(Terminal Expression):实现与语法中的终结符相关的解释操作。
  3. 非终结符表达式(Non-terminal Expression):实现语法中的规则,通常包含对其他表达式的引用。
  4. 上下文(Context):包含解释器需要的全局信息。
  5. 客户端(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}
}

改造后的好处

  1. 规则可配置化
    可以将促销规则抽象为表达式对象,甚至通过配置文件或数据库动态生成规则树,无需修改代码。
    例如:将规则 (isVip && orderAmount >= 100) || (productCategory == "electronics") 存储为 JSON,动态解析为表达式对象。
  2. 灵活组合条件
    通过组合AndExpressionOrExpression,可以轻松实现复杂的逻辑。
  3. 易于扩展
    新增条件(例如“用户年龄 ≤ 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 的核心流程分为两个阶段

  1. 解析阶段:将字符串表达式(如 “2 + 3 * 4”)解析为 抽象语法树(AST),树中的每个节点对应一个表达式对象。
  2. 执行阶段:递归遍历 AST,解释每个节点并计算结果。这一过程完美契合解释器模式的 语法树解释执行 思想。
     OpPlus/    \"2"   OpMultiply/    \"3"    "4"

下面是对源码的分析:

抽象表达式Expression 接口,所有具体表达式(如字面量、运算符、方法调用)都实现此接口。

public interface Expression {// 核心方法:解释表达式并返回结果Object getValue() throws EvaluationException;// 其他重载方法(支持上下文、目标类型等)
}

终结符表达式示例LiteralExpressionLiteralExpression 直接解析字面量(如 “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}
}

三、总结

解释器模式的优点

  1. 易于扩展语法规则
    通过添加新的表达式类,可以轻松扩展语法规则,符合 开闭原则(对扩展开放,对修改封闭)。
  2. 实现简单语法解析
    对于简单的语法规则,解释器模式提供了一种直观的实现方式,将语法规则分解为多个表达式类。
  3. 解耦语法解析与执行
    将语法解析逻辑封装在表达式类中,与业务逻辑解耦,使代码更清晰、更易维护。
  4. 适合领域特定语言(DSL)
    解释器模式非常适合实现 领域特定语言(如规则引擎、查询语言等),能够将复杂的业务规则抽象为表达式树。
  5. 灵活性
    可以通过组合不同的表达式类,动态构建复杂的语法树,支持运行时修改规则。

解释器模式的缺点

  1. 复杂性高
    对于复杂的语法规则,解释器模式会导致类的数量急剧增加(每个规则都需要一个表达式类),增加系统复杂性。
  2. 性能问题
    解释器模式通常采用递归解释执行,性能较低,不适合对性能要求较高的场景。
  3. 难以维护
    随着语法规则的增加,表达式类的数量会变得庞大,导致代码难以维护。
  4. 不适合复杂语法
    解释器模式更适合处理简单的语法规则,对于复杂的语法(如编程语言),使用解释器模式会变得非常笨拙。
  5. 学习成本高
    需要开发者熟悉语法树的设计和递归解释执行的原理,增加了学习和实现的难度。

解释器模式的适用场景

  1. 领域特定语言(DSL)
    当需要实现一种简单的领域特定语言时,解释器模式是一种自然的选择。例如:
    • 规则引擎(如促销规则、风控规则)。
    • 查询语言(如 SQL 条件解析)。
    • 模板引擎(如动态生成邮件内容)。
  2. 需要动态解析和执行规则的场景
    当规则需要动态配置(如从数据库或配置文件中加载)并在运行时解析执行时,解释器模式非常适用。
  3. 语法规则相对固定且简单
    如果语法规则不会频繁变化,且规则数量较少,解释器模式可以很好地满足需求。
  4. 不适合使用编译器或解析器生成工具的场景
    对于简单的语法规则,使用编译器或解析器生成工具(如 ANTLR)可能过于复杂,解释器模式提供了一种轻量级的解决方案。

相关文章:

【设计模式】【行为型模式】解释器模式(Interpreter)

&#x1f44b;hi&#xff0c;我不是一名外包公司的员工&#xff0c;也不会偷吃茶水间的零食&#xff0c;我的梦想是能写高端CRUD &#x1f525; 2025本人正在沉淀中… 博客更新速度 &#x1f44d; 欢迎点赞、收藏、关注&#xff0c;跟上我的更新节奏 &#x1f3b5; 当你的天空突…...

修改OnlyOffice编辑器默认字体

通过Docker修改OnlyOffice编辑器默认字体 问题描述详细方案1. 删除原生字体文件2. 创建字体目录3. 复制字体文件到容器中4. 执行字体更新脚本5. 重新启动容器 注意事项 问题描述 在OnlyOffice中&#xff0c;编辑器的默认字体可能不符合公司或个人的需求&#xff0c;通常会使用…...

React echarts柱状图点击某个柱子跳转页面

绘制echarts柱状图 在 ECharts 中&#xff0c;如果你想要在点击柱状图的某个柱子时进行页面跳转&#xff0c;你可以通过设置 series 中的 data 属性中的 itemStyle 或者使用 series 的 label 属性中的 emphasis 属性来实现。但是&#xff0c;直接在柱状图中实现点击跳转通常涉…...

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相关组件 &#xff08;1&#xff09;Elasticsearch是一款非常强大的开源搜索引擎&#xff0c;可以帮助我们从海量数据中快速找到需要的内容。Elasticsearch结合kibana、Logstash、Beats组件 也就是elastic stack&#xff08;ELK&#xff09; 广泛应…...

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的压缩包点下载&#xff0c;并解压 2. fffmpeg下载 ffmpeg官方下载 …...

【kafka系列】broker

目录 Broker 接收生产者消息和返回消息给消费者的流程逻辑分析 Broker 处理生产者消息的核心流程 Broker 处理消费者消息的核心流程 关键点总结 Broker 接收生产者消息和返回消息给消费者的流程逻辑分析 Broker 处理生产者消息的核心流程 接收请求 Broker 的 SocketServer …...

用大模型学大模型05-线性回归

deepseek.com:多元线性回归的目标函数&#xff0c;损失函数&#xff0c;梯度下降 标量和矩阵形式的数学推导&#xff0c;pytorch真实能跑的代码案例以及模型,数据&#xff0c;预测结果的可视化展示&#xff0c; 模型应用场景和优缺点&#xff0c;及如何改进解决及改进方法数据推…...

Python实现AWS Fargate自动化部署系统

一、背景介绍 在现代云原生应用开发中,自动化部署是提高开发效率和保证部署质量的关键。AWS Fargate作为一项无服务器计算引擎,可以让我们专注于应用程序开发而无需管理底层基础设施。本文将详细介绍如何使用Python实现AWS Fargate的完整自动化部署流程。 © ivwdcwso (ID…...

国产编辑器EverEdit - 上下翻滚不迷路(历史编辑位置、历史光标位置回溯功能)

1 光标位置跳转 1.1 应用场景 某些场景下&#xff0c;用户从当前编辑位置跳转到别的位置查阅信息&#xff0c;如果要快速跳转回之前编辑位置&#xff0c;则可以使用光标跳转相关功能。 1.2 使用方法 1.2.1 上一个编辑位置 跳转到上一个编辑位置&#xff0c;即文本修改过的位…...

今日写题work05

题目&#xff1a;用队列实现栈 思路 队列的特点是先进先出&#xff0c;而栈的特点是后进先出。所以想要用队列实现模拟栈&#xff0c;我们可以使用两个队列&#xff0c;一个队列负责压栈&#xff0c;一个队列负责出栈。压栈很简单就是检空再调用队列的push就好&#xff0c;那出…...

[C++语法基础与基本概念] std::function与可调用对象

std::function与可调用对象 函数指针lambda表达式std::function与std::bind仿函数总结std::thread与可调用对象std::async与可调用对象回调函数 可调用对象是指那些像函数一样可以直接被调用的对象&#xff0c;他们广泛用于C的算法&#xff0c;回调&#xff0c;事件处理等机制。…...

两个实用且热门的 Python 爬虫案例,结合动态/静态网页抓取和反爬策略,附带详细代码和实现说明

在这个瞬息万变的世界里&#xff0c;保持一颗探索的心&#xff0c;永远怀揣梦想前行。即使有时会迷失方向&#xff0c;也不要忘记内心深处那盏指引你前进的明灯。它代表着你的希望、你的信念以及对未来的无限憧憬。每一个不曾起舞的日子&#xff0c;都是对生命的辜负&#xff1…...

华象新闻 | 2月20日前谨慎升级 PostgreSQL 版本

各位 PostgreSQL 用户&#xff0c;建议近期进行升级 PostgreSQL 版本。 2月20日计划进行非周期性版本发布 PostgreSQL全球开发团队计划于2025年2月20日进行一次非周期性发布&#xff0c;以解决2025年2月13日更新版本中引入的一个回归问题。 2月13日的更新版本包括了17.3、16.7、…...

跳跃游戏 II - 贪心算法解法

问题描述&#xff1a; 给定一个长度为 n 的 0 索引整数数组 nums&#xff0c;我们从数组的第一个元素 nums[0] 开始。每个元素 nums[i] 表示从索引 i 可以跳跃的最大长度&#xff0c;换句话说&#xff0c;从位置 i&#xff0c;你可以跳到位置 i j&#xff0c;其中 0 < j &…...

图像质量评价指标-UCIQE-UIQM

一、评价指标UCIQE 在文章《An underwater color image quality evaluation metric》中&#xff0c;提到的了评价指标UCIQE&#xff08;Underwater Colour Image Quality Evaluation&#xff09;&#xff0c;是一种无参考图像质量评价指标&#xff0c;主要用于评估水下图像的质…...

CentOS上安装WordPress

在CentOS上安装WordPress是一个相对直接的过程&#xff0c;可以通过多种方法完成&#xff0c;包括使用LAMP&#xff08;Linux, Apache, MySQL, PHP&#xff09;栈或使用更现代的LEMP&#xff08;Linux, Nginx, MySQL, PHP&#xff09;栈。 我选择的是&#xff08;Linux, Nginx…...

Spring Boot 原理分析

spring-boot.version&#xff1a;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 两种上传方式详细讲解)

前言&#xff1a;Git 与 GitHub 的区别与联系 在学习如何将本地项目上传到 GitHub 之前&#xff0c;先来弄清楚 Git 和 GitHub 的区别以及它们之间的联系。 对比项GitGitHub定义分布式版本控制系统&#xff08;DVCS&#xff09;&#xff0c;用于本地和远程管理代码版本托管 G…...

树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法

树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作&#xff0c;无需更改相机配置。但是&#xff0c;一…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

R语言AI模型部署方案:精准离线运行详解

R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...

大型活动交通拥堵治理的视觉算法应用

大型活动下智慧交通的视觉分析应用 一、背景与挑战 大型活动&#xff08;如演唱会、马拉松赛事、高考中考等&#xff09;期间&#xff0c;城市交通面临瞬时人流车流激增、传统摄像头模糊、交通拥堵识别滞后等问题。以演唱会为例&#xff0c;暖城商圈曾因观众集中离场导致周边…...

Java如何权衡是使用无序的数组还是有序的数组

在 Java 中,选择有序数组还是无序数组取决于具体场景的性能需求与操作特点。以下是关键权衡因素及决策指南: ⚖️ 核心权衡维度 维度有序数组无序数组查询性能二分查找 O(log n) ✅线性扫描 O(n) ❌插入/删除需移位维护顺序 O(n) ❌直接操作尾部 O(1) ✅内存开销与无序数组相…...

关于nvm与node.js

1 安装nvm 安装过程中手动修改 nvm的安装路径&#xff0c; 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解&#xff0c;但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后&#xff0c;通常在该文件中会出现以下配置&…...

ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放

简介 前面两期文章我们介绍了I2S的读取和写入&#xff0c;一个是通过INMP441麦克风模块采集音频&#xff0c;一个是通过PCM5102A模块播放音频&#xff0c;那如果我们将两者结合起来&#xff0c;将麦克风采集到的音频通过PCM5102A播放&#xff0c;是不是就可以做一个扩音器了呢…...

P3 QT项目----记事本(3.8)

3.8 记事本项目总结 项目源码 1.main.cpp #include "widget.h" #include <QApplication> int main(int argc, char *argv[]) {QApplication a(argc, argv);Widget w;w.show();return a.exec(); } 2.widget.cpp #include "widget.h" #include &q…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

css3笔记 (1) 自用

outline: none 用于移除元素获得焦点时默认的轮廓线 broder:0 用于移除边框 font-size&#xff1a;0 用于设置字体不显示 list-style: none 消除<li> 标签默认样式 margin: xx auto 版心居中 width:100% 通栏 vertical-align 作用于行内元素 / 表格单元格&#xff…...