👋hi,我不是一名外包公司的员工,也不会偷吃茶水间的零食,我的梦想是能写高端CRUD
🔥 2025本人正在沉淀中... 博客更新速度++
👍 欢迎点赞、收藏、关注,跟上我的更新节奏
🎵 当你的天空突然下了大雨,那是我在为你炸乌云
文章目录
- 一、入门
- 二、解释器模式在框架源中的运用
- [Spring 表达式语言(SpEL)](#Spring 表达式语言(SpEL))
- 三、总结
一、入门
什么是解释器模式?
解释器模式(Interpreter Pattern)是一种行为设计模式,用于定义语言的语法表示,并提供一个解释器来处理该语法。它通常用于需要解释和执行特定语言或表达式的场景。
为什么要解释器模式?
假设一个电商平台需要实现动态的促销规则,例如:
- 规则1:用户是 VIP 且 订单金额 ≥ 100 元 → 享受 20 元优惠。
- 规则2:商品类别是 "电子产品" 或 库存量 > 50 → 允许参加秒杀活动。
这些规则需要灵活配置,并且随着业务发展可能会新增条件(例如添加"用户年龄 ≤ 30 岁"等)。
下面是没有用解释器模式的实现代码。
java
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
接口。
java
interface RuleExpression {
boolean interpret(Map<String, Object> context);
}
终结符表达式(Terminal Expression) : IsVipExpressio
类,判断用户是否为vip;OrderAmountExpression
判断订单金额是否≥指定值;ProductCategoryExpression
类,判断商品类别是否匹配。
java
// 终结符表达式:用户是否是VIP
class IsVipExpression implements RuleExpression {
@Override
public 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;
}
@Override
public 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;
}
@Override
public boolean interpret(Map<String, Object> context) {
String actualCategory = (String) context.get("productCategory");
return actualCategory.equals(category);
}
}
非终结符表达式(组合条件) :AndExpression
类,逻辑"与"操作。OrExpression
类,逻辑"或"操作。
java
// 非终结符表达式:逻辑"与"操作
class AndExpression implements RuleExpression {
private RuleExpression expr1;
private RuleExpression expr2;
public AndExpression(RuleExpression expr1, RuleExpression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public 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;
}
@Override
public boolean interpret(Map<String, Object> context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}
客户端,假设需要判断用户是否满足,规则1(VIP 且订单金额≥100元),客户端是负责 构建规则表达式 并 调用解释器执行规则 的部分。
java
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 >= 100
RuleExpression 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
注解中注入动态值
java
@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,解释每个节点并计算结果。这一过程完美契合解释器模式的 语法树解释执行 思想。
shell
OpPlus
/ \
"2" OpMultiply
/ \
"3" "4"
下面是对源码的分析:
抽象表达式 :Expression
接口,所有具体表达式(如字面量、运算符、方法调用)都实现此接口。
java
public interface Expression {
// 核心方法:解释表达式并返回结果
Object getValue() throws EvaluationException;
// 其他重载方法(支持上下文、目标类型等)
}
终结符表达式示例 :LiteralExpression
,LiteralExpression
直接解析字面量(如 "100"),无需依赖其他表达式。
java
public class LiteralExpression implements Expression {
private final String literalValue;
public LiteralExpression(String literalValue) {
this.literalValue = literalValue;
}
@Override
public Object getValue() {
// 直接返回字面量值(如 "42" 转换为整数)
return this.literalValue;
}
}
非终结符表达式示例 :OpPlus
(加法操作)。OpPlus
组合了左、右两个操作数(可能是其他表达式对象),递归解释执行。
java
public class OpPlus extends Operator {
@Override
public TypedValue getValueInternal(ExpressionState state) throws EvaluationException {
// 递归获取左、右操作数的值
Object leftOperand = getLeftOperand().getValueInternal(state).getValue();
Object rightOperand = getRightOperand().getValueInternal(state).getValue();
// 执行加法操作
return new TypedValue(leftOperand + rightOperand);
}
}
上下文 :EvaluationContex
t接口,StandardEvaluationContext
是默认实现,提供变量绑定和类型支持。
java
public interface EvaluationContext {
// 获取变量值(如 "#user")
Object lookupVariable(String name);
// 获取类型转换器、函数等
TypeConverter getTypeConverter();
}
客户端 :SpelExpressionParser:SpelExpressionParser
负责将字符串转换为 Expression
对象(语法树的根节点)。
java
public class SpelExpressionParser {
// 解析字符串为 Expression 对象(语法树)
public Expression parseExpression(String expressionString) {
// 使用 Tokenizer 分词,Parser 构建 AST
return this.doParseExpression(expressionString);
}
}
测试类
java
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)可能过于复杂,解释器模式提供了一种轻量级的解决方案。