欢迎你来读这篇博客,这篇博客主要是关于解释器模式。 其中包括解释器模式的核心思想、适用场景、文法表达式、终结符表达式、非终结符表达式、优缺点、与策略模式/责任链模式/规则引擎的区别,以及 Java 后端开发中订单优惠规则表达式解释器的完整案例。
序言 解释器模式是 23 种设计模式里相对“冷门”的一个。
很多人学设计模式时看到它,第一反应可能是:
这东西是不是在写编译器?
这个理解不完全错。
解释器模式确实和“语言”“文法”“表达式解析”有关。
但它不一定非要写一个完整编程语言。
在 Java 后端开发里,我们也经常会遇到一些“小语言”或“小表达式”:
优惠规则表达式;
权限规则表达式;
数据过滤表达式;
报表计算公式;
工作流条件表达式;
风控规则表达式;
搜索查询表达式;
配置中心条件表达式。
比如电商系统里有一条优惠规则:
1 amount >= 100 AND userLevel == VIP
它表示:
订单金额大于等于 100,并且用户等级是 VIP,才可以使用这个优惠。
如果规则很少,直接写 if else 就行。
但如果规则很多,而且需要动态配置,就不能每次新增规则都改代码、发版。
这时就可以把规则写成表达式,然后由程序解释执行。
解释器模式解决的就是这类问题:
为某种语言定义文法,并实现一个解释器来解释这种语言中的句子。
说得简单点:
解释器模式就是让程序读懂你定义的一套规则表达式。
当然,它不是万能钥匙。
如果表达式复杂到像 SQL、JavaScript、正则表达式、复杂规则引擎,那就别自己硬写解释器了。能用成熟框架就用成熟框架,别拿青春给 parser 填坑。
正文 chapter 1:什么是解释器模式 解释器模式,英文是 Interpreter Pattern ,属于行为型设计模式。
它的定义是:
给定一种语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
这句话听起来很学术。
可以拆成三层:
你定义一种“小语言”;
你规定这种语言怎么写,也就是文法;
你写代码解释这种语言,并执行对应逻辑。
例如:
1 amount >= 100 AND userLevel == VIP
这就是一条规则语言。
它的文法大概包含:
比较表达式:amount >= 100
等值表达式:userLevel == VIP
逻辑表达式:AND
变量:amount、userLevel
常量:100、VIP
解释器要做的是:
根据当前上下文,判断这条表达式是否成立。
例如上下文是:
1 2 amount = 150 userLevel = VIP
解释结果就是:
如果上下文是:
1 2 amount = 80 userLevel = VIP
解释结果就是:
chapter 2:解释器模式的核心角色 解释器模式一般包含四个角色。
1. AbstractExpression 抽象表达式 定义解释方法。
1 2 3 4 public interface Expression { boolean interpret (Context context) ; }
2. TerminalExpression 终结符表达式 表示文法中最基本的元素。
例如:
3. NonTerminalExpression 非终结符表达式 由多个表达式组合而成。
例如:
AND 表达式;
OR 表达式;
NOT 表达式;
加减乘除表达式;
括号组合表达式。
4. Context 上下文 保存解释过程中需要的数据。
例如订单金额、用户等级、商品类型等。
1 2 3 4 public class RuleContext { private final Map<String, Object> variables; }
结构可以理解为:
1 2 3 4 5 6 7 8 9 Expression ▲ │ ├── TerminalExpression │ └── NonTerminalExpression │ ├── Expression left └── Expression right
chapter 3:终结符和非终结符是什么 解释器模式里经常出现两个词:
这两个词来自编译原理,听起来很吓人,但其实不复杂。
1. 终结符表达式 终结符表达式就是不能再拆的基本表达式。
例如:
它是一个基础条件。
再比如:
它也是一个基础条件。
这些可以看作终结符表达式。
2. 非终结符表达式 非终结符表达式是由其他表达式组合而成的表达式。
例如:
1 amount >= 100 AND userLevel == VIP
它由两个基础表达式通过 AND 组合而成。
这里的 AND 表达式就是非终结符表达式。
再比如:
1 (amount >= 100 AND userLevel == VIP) OR couponType == NEW_USER
这是更复杂的非终结符表达式。
3. 简单类比 可以这样理解:
解释器模式就是把规则拆成一棵表达式树,然后递归解释这棵树。
chapter 4:解释器模式适合解决什么问题 解释器模式适合解决这类问题:
某类规则可以用一种简单语言表达,而且这类规则需要频繁变化或动态配置。
例如:
1. 业务规则 1 amount >= 100 AND userLevel == VIP
2. 权限表达式 1 hasRole(ADMIN) OR hasPermission(order:refund)
3. 搜索表达式 1 status = PAID AND amount > 100
4. 风控表达式 1 ipRisk == HIGH OR deviceRisk == HIGH
5. 报表公式 1 grossProfit = revenue - cost
6. 工作流条件 1 amount > 10000 AND department == FINANCE
如果规则只是偶尔变,写 if else 就够了。
如果规则很多,而且需要配置化、动态化、组合化,就可以考虑解释器模式或成熟表达式引擎。
chapter 5:不用解释器模式会怎样 假设优惠规则写死在代码里:
1 2 3 4 5 6 7 8 public boolean match (Order order, User user) { if (order.getAmount().compareTo(new BigDecimal ("100" )) >= 0 && "VIP" .equals(user.getLevel())) { return true ; } return false ; }
后来新增规则:
1 amount >= 200 AND productCategory == BOOK
继续加 if else:
1 2 3 4 if (order.getAmount().compareTo(new BigDecimal ("200" )) >= 0 && "BOOK" .equals(order.getProductCategory())) { return true ; }
再后来规则越来越多:
1 2 3 4 amount >= 100 AND userLevel == VIP amount >= 200 AND productCategory == BOOK amount >= 300 AND city == HANGZHOU amount >= 500 AND userTag == HIGH_VALUE
代码就会变成规则泥潭。
如果运营希望在后台配置规则,那代码写死就更不合适了。
这时更合理的方式是让规则变成配置:
1 amount >= 100 AND userLevel == VIP
然后由解释器解释执行。
这样新增规则不一定需要改代码。
chapter 6:案例背景:订单优惠规则解释器 下面用一个 Java 后端场景来讲解释器模式。
假设我们正在做一个优惠券系统。
优惠券可以配置使用规则。
例如:
表示订单金额满 100 可用。
表示用户等级是 VIP 可用。
1 amount >= 100 AND userLevel == VIP
表示订单金额满 100,并且用户等级是 VIP 才可用。
1 amount >= 100 OR userLevel == VIP
表示订单金额满 100,或者用户等级是 VIP 就可用。
为了简化,我们先支持以下表达式:
大于等于:>=
小于等于:<=
等于:==
逻辑与:AND
逻辑或:OR
暂时不支持括号和复杂优先级。
这已经足够说明解释器模式的核心思想。
chapter 7:定义规则上下文 RuleContext 规则解释时需要上下文。
例如:
1 2 3 amount = 150 userLevel = VIP productCategory = BOOK
定义上下文:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.math.BigDecimal;import java.util.HashMap;import java.util.Map;public class RuleContext { private final Map<String, Object> variables = new HashMap <>(); public void put (String name, Object value) { variables.put(name, value); } public Object get (String name) { return variables.get(name); } public BigDecimal getBigDecimal (String name) { Object value = variables.get(name); if (value == null ) { throw new IllegalArgumentException ("Variable not found: " + name); } if (value instanceof BigDecimal decimal) { return decimal; } if (value instanceof Number number) { return BigDecimal.valueOf(number.doubleValue()); } return new BigDecimal (value.toString()); } public String getString (String name) { Object value = variables.get(name); if (value == null ) { throw new IllegalArgumentException ("Variable not found: " + name); } return value.toString(); } }
这个上下文负责保存变量值。
解释器在解释表达式时,会从上下文中读取变量。
chapter 8:定义表达式接口 Expression 1 2 3 4 public interface Expression { boolean interpret (RuleContext context) ; }
所有表达式都实现这个接口。
每个表达式都能根据上下文解释出一个布尔结果。
例如:
结果可能是:
也可能是:
chapter 9:定义大于等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class GreaterThanOrEqualExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public GreaterThanOrEqualExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) >= 0 ; } }
这个表达式对应:
其中:
variableName = amount
expectedValue = 100
解释时,从上下文中取出 amount,然后和 100 比较。
chapter 10:定义小于等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class LessThanOrEqualExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public LessThanOrEqualExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) <= 0 ; } }
它对应:
chapter 11:定义等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class EqualExpression implements Expression { private final String variableName; private final String expectedValue; public EqualExpression (String variableName, String expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { String actualValue = context.getString(variableName); return expectedValue.equals(actualValue); } }
它对应:
或者:
这是一个终结符表达式。
因为它不能再继续拆成更小的业务表达式。
chapter 12:定义 AND 表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class AndExpression implements Expression { private final Expression left; private final Expression right; public AndExpression (Expression left, Expression right) { this .left = left; this .right = right; } @Override public boolean interpret (RuleContext context) { return left.interpret(context) && right.interpret(context); } }
它对应:
例如:
1 amount >= 100 AND userLevel == VIP
这里:
left 是 amount >= 100
right 是 userLevel == VIP
AndExpression 是非终结符表达式。
因为它由两个表达式组合而成。
chapter 13:定义 OR 表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class OrExpression implements Expression { private final Expression left; private final Expression right; public OrExpression (Expression left, Expression right) { this .left = left; this .right = right; } @Override public boolean interpret (RuleContext context) { return left.interpret(context) || right.interpret(context); } }
它对应:
例如:
1 amount >= 100 OR userLevel == VIP
chapter 14:手动构建表达式树 先不写解析器,手动构建表达式树。
规则:
1 amount >= 100 AND userLevel == VIP
对应代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 import java.math.BigDecimal;public class InterpreterDemo { public static void main (String[] args) { Expression amountExpression = new GreaterThanOrEqualExpression ( "amount" , new BigDecimal ("100" ) ); Expression userLevelExpression = new EqualExpression ( "userLevel" , "VIP" ); Expression ruleExpression = new AndExpression ( amountExpression, userLevelExpression ); RuleContext context = new RuleContext (); context.put("amount" , new BigDecimal ("150" )); context.put("userLevel" , "VIP" ); boolean result = ruleExpression.interpret(context); System.out.println("规则是否匹配:" + result); } }
输出:
如果把 amount 改成 80:
1 context.put("amount" , new BigDecimal ("80" ));
结果就是:
这就是解释器模式最核心的执行方式:
表达式对象组成表达式树,递归解释上下文。
chapter 15:表达式树结构 刚才这条规则:
1 amount >= 100 AND userLevel == VIP
可以表示成一棵树:
1 2 3 AND / \ amount >= 100 userLevel == VIP
代码上就是:
1 2 3 4 new AndExpression ( new GreaterThanOrEqualExpression ("amount" , new BigDecimal ("100" )), new EqualExpression ("userLevel" , "VIP" ) )
解释时:
解释左表达式;
解释右表达式;
对结果做 && 运算。
如果规则是:
1 amount >= 100 OR userLevel == VIP
树就是:
1 2 3 OR / \ amount >= 100 userLevel == VIP
这就是解释器模式很典型的表达式树思想。
chapter 16:为什么还需要解析器 手动构建表达式树虽然能说明原理,但真实业务不可能让运营或配置人员写 Java 代码。
真实规则一般来自配置:
1 amount >= 100 AND userLevel == VIP
所以我们还需要一个简单解析器,把字符串规则转换成表达式对象。
也就是:
1 Expression expression = parser.parse("amount >= 100 AND userLevel == VIP" );
然后:
1 boolean result = expression.interpret(context);
解析器不一定是 GoF 解释器模式的核心角色,但在真实落地中非常常见。
没有解析器,解释器模式很难配置化。
chapter 17:实现简单规则解析器 为了简化,我们先支持简单表达式:
1 2 3 4 amount >= 100 userLevel == VIP amount >= 100 AND userLevel == VIP amount >= 100 OR userLevel == VIP
暂时不支持括号,也不支持混合多个 AND/OR 的复杂优先级。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import java.math.BigDecimal;public class SimpleRuleParser { public Expression parse (String rule) { if (rule == null || rule.isBlank()) { throw new IllegalArgumentException ("rule can not be blank" ); } if (rule.contains(" AND " )) { String[] parts = rule.split(" AND " , 2 ); return new AndExpression ( parse(parts[0 ]), parse(parts[1 ]) ); } if (rule.contains(" OR " )) { String[] parts = rule.split(" OR " , 2 ); return new OrExpression ( parse(parts[0 ]), parse(parts[1 ]) ); } return parseSingleExpression(rule); } private Expression parseSingleExpression (String rule) { if (rule.contains(">=" )) { String[] parts = rule.split(">=" , 2 ); return new GreaterThanOrEqualExpression ( parts[0 ].trim(), new BigDecimal (parts[1 ].trim()) ); } if (rule.contains("<=" )) { String[] parts = rule.split("<=" , 2 ); return new LessThanOrEqualExpression ( parts[0 ].trim(), new BigDecimal (parts[1 ].trim()) ); } if (rule.contains("==" )) { String[] parts = rule.split("==" , 2 ); return new EqualExpression ( parts[0 ].trim(), parts[1 ].trim() ); } throw new IllegalArgumentException ("Unsupported rule expression: " + rule); } }
这个解析器很简单,但已经能说明解释器模式的完整流程。
chapter 18:使用解析器执行规则 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.math.BigDecimal;public class RuleParserDemo { public static void main (String[] args) { String rule = "amount >= 100 AND userLevel == VIP" ; SimpleRuleParser parser = new SimpleRuleParser (); Expression expression = parser.parse(rule); RuleContext context = new RuleContext (); context.put("amount" , new BigDecimal ("150" )); context.put("userLevel" , "VIP" ); boolean result = expression.interpret(context); System.out.println("规则:" + rule); System.out.println("是否匹配:" + result); } }
输出:
1 2 规则:amount >= 100 AND userLevel == VIP 是否匹配:true
现在规则可以来自:
数据库;
配置中心;
管理后台;
JSON 配置;
YAML 配置;
MQ 消息。
这就是解释器模式在业务规则中的价值。
chapter 19:扩展 NOT 表达式 如果要支持:
可以增加一个 NotExpression。
1 2 3 4 5 6 7 8 9 10 11 12 13 public class NotExpression implements Expression { private final Expression expression; public NotExpression (Expression expression) { this .expression = expression; } @Override public boolean interpret (RuleContext context) { return !expression.interpret(context); } }
解析器中增加:
1 2 3 if (rule.startsWith("NOT " )) { return new NotExpression (parse(rule.substring(4 ).trim())); }
这样就支持了逻辑非。
解释器模式的扩展方式就是:
新增一种语法,就新增一种表达式类。
这符合开闭原则的一部分。
但也要注意,如果语法越来越复杂,表达式类会越来越多。
chapter 20:扩展大于表达式和小于表达式 如果需要支持:
1 2 amount > 100 amount < 500
可以新增两个表达式。
大于表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class GreaterThanExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public GreaterThanExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) > 0 ; } }
小于表达式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class LessThanExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public LessThanExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) < 0 ; } }
然后解析器扩展即可。
chapter 21:解释器模式中的文法 解释器模式经常会提到“文法”。
文法就是规则怎么写。
比如我们可以定义一个简单文法:
1 2 3 4 5 6 7 8 expression ::= condition | expression AND expression | expression OR expression | NOT expression condition ::= variable operator value operator ::= >= | <= | == | > | <
这表示:
表达式可以是一个条件;
表达式可以由 AND 连接;
表达式可以由 OR 连接;
表达式可以由 NOT 修饰;
条件由变量、操作符和值组成。
例如:
符合:
1 condition ::= variable operator value
再比如:
1 amount >= 100 AND userLevel == VIP
符合:
1 expression ::= expression AND expression
如果你发现某类业务规则可以写出稳定文法,就可以考虑解释器模式。
chapter 22:解释器模式和抽象语法树 AST 表达式通常会被解析成一棵树。
这棵树可以叫表达式树,也可以叫 AST,Abstract Syntax Tree,抽象语法树。
比如:
1 amount >= 100 AND userLevel == VIP
对应:
1 2 3 AndExpression ├── GreaterThanOrEqualExpression(amount, 100) └── EqualExpression(userLevel, VIP)
在代码中:
1 2 3 4 Expression expression = new AndExpression ( new GreaterThanOrEqualExpression ("amount" , new BigDecimal ("100" )), new EqualExpression ("userLevel" , "VIP" ) );
解释执行时,就是递归遍历这棵树。
1 expression.interpret(context);
这也是解释器模式和编译原理关系比较近的原因。
不过业务中不用把它想得太复杂。
你可以把 AST 理解成:
把字符串规则拆成一组对象结构。
chapter 23:Spring Boot 中落地解释器模式 在 Spring Boot 中,解释器模式可以用于优惠规则、风控规则、权限表达式等场景。
一个常见结构是:
1 2 3 4 5 RuleExpression RuleContext RuleParser RuleEngine CouponRuleService
例如:
1 2 3 4 5 6 7 8 9 10 11 @Service public class CouponRuleService { private final SimpleRuleParser ruleParser = new SimpleRuleParser (); public boolean match (String rule, RuleContext context) { Expression expression = ruleParser.parse(rule); return expression.interpret(context); } }
更完整一点:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 import org.springframework.stereotype.Service;@Service public class CouponRuleService { private final RuleExpressionCache ruleExpressionCache; public CouponRuleService (RuleExpressionCache ruleExpressionCache) { this .ruleExpressionCache = ruleExpressionCache; } public boolean match (Long couponId, String rule, RuleContext context) { Expression expression = ruleExpressionCache.getOrParse(couponId, rule); return expression.interpret(context); } }
这里可以加缓存。
因为规则字符串每次解析成表达式树也有成本。
chapter 24:表达式缓存 规则表达式通常不会频繁变化。
可以缓存解析后的表达式树。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.util.Map;import java.util.concurrent.ConcurrentHashMap;public class RuleExpressionCache { private final SimpleRuleParser parser = new SimpleRuleParser (); private final Map<Long, Expression> cache = new ConcurrentHashMap <>(); public Expression getOrParse (Long ruleId, String rule) { return cache.computeIfAbsent(ruleId, id -> parser.parse(rule)); } public void remove (Long ruleId) { cache.remove(ruleId); } public void clear () { cache.clear(); } }
使用方式:
1 2 3 Expression expression = cache.getOrParse(couponId, rule);boolean matched = expression.interpret(context);
如果规则变更,可以删除缓存:
真实项目中还要考虑:
多实例缓存刷新;
配置中心变更;
数据库规则更新;
本地缓存过期;
表达式版本号;
规则灰度发布。
chapter 25:完整优惠券匹配案例 定义优惠券对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Coupon { private final Long couponId; private final String name; private final String ruleExpression; public Coupon (Long couponId, String name, String ruleExpression) { this .couponId = couponId; this .name = name; this .ruleExpression = ruleExpression; } public Long getCouponId () { return couponId; } public String getName () { return name; } public String getRuleExpression () { return ruleExpression; } }
优惠券匹配服务:
1 2 3 4 5 6 7 8 9 10 11 12 13 public class CouponMatcher { private final RuleExpressionCache expressionCache = new RuleExpressionCache (); public boolean match (Coupon coupon, RuleContext context) { Expression expression = expressionCache.getOrParse( coupon.getCouponId(), coupon.getRuleExpression() ); return expression.interpret(context); } }
客户端:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.math.BigDecimal;import java.util.List;public class CouponMatcherDemo { public static void main (String[] args) { List<Coupon> coupons = List.of( new Coupon (1L , "满 100 VIP 专享券" , "amount >= 100 AND userLevel == VIP" ), new Coupon (2L , "满 200 图书券" , "amount >= 200 AND productCategory == BOOK" ), new Coupon (3L , "普通满 50 券" , "amount >= 50" ) ); RuleContext context = new RuleContext (); context.put("amount" , new BigDecimal ("150" )); context.put("userLevel" , "VIP" ); context.put("productCategory" , "DIGITAL" ); CouponMatcher matcher = new CouponMatcher (); for (Coupon coupon : coupons) { boolean matched = matcher.match(coupon, context); System.out.println("优惠券:" + coupon.getName() + ",是否可用:" + matched); } } }
输出类似:
1 2 3 优惠券:满 100 VIP 专享券,是否可用:true 优惠券:满 200 图书券,是否可用:false 优惠券:普通满 50 券,是否可用:true
这个案例就是解释器模式的典型业务落地:
规则用字符串表达;
解析器把规则转成表达式对象;
表达式根据上下文解释执行;
业务服务拿到匹配结果。
chapter 26:解释器模式和策略模式的区别 解释器模式和策略模式都可以处理变化的业务规则。
但它们关注点不同。
对比项
解释器模式
策略模式
核心目的
解释一种规则语言
封装可替换算法
规则表达
通常是表达式或 DSL
通常是一个类
扩展方式
新增语法或表达式
新增策略类
是否适合动态配置
适合
一般需要代码实现
复杂度
较高
较低
示例
amount >= 100 AND userLevel == VIP
VipDiscountStrategy
如果规则可以由运营配置,并且规则组合很多,解释器模式更合适。
如果规则是有限几种算法,例如普通折扣、会员折扣、新人折扣,策略模式更简单。
chapter 27:解释器模式和责任链模式的区别 解释器模式和责任链模式都能处理规则,但方式不同。
对比项
解释器模式
责任链模式
关注点
解释表达式
多个处理器顺序处理请求
结构
表达式树
处理器链
输入
规则表达式 + 上下文
请求上下文
执行方式
递归解释表达式
按顺序执行 Handler
示例
amount >= 100 AND userLevel == VIP
参数校验 -> 库存校验 -> 风控校验
如果规则可以表达成一条可解析的表达式,用解释器。
如果规则是多个步骤依次处理,用责任链。
例如:
1 amount >= 100 AND userLevel == VIP
适合解释器。
1 参数校验 -> 用户校验 -> 商品校验 -> 库存校验
适合责任链。
chapter 28:解释器模式和规则引擎的区别 解释器模式可以实现简单规则引擎,但它不等于完整规则引擎。
成熟规则引擎通常支持:
规则管理;
优先级;
冲突解决;
事实模型;
推理;
规则分组;
动态加载;
规则版本;
规则命中解释;
复杂条件组合;
性能优化。
解释器模式只是提供一种基础思想:
如何解释一套规则表达式。
如果你的业务规则非常复杂,建议考虑成熟方案,例如:
Aviator;
MVEL;
SpEL;
Drools;
ANTLR 自定义 DSL;
Janino;
Easy Rules;
QLExpress。
自己写解释器适合简单规则。
复杂规则自己造轮子,很容易造出一个方形轮子,跑起来咣当咣当还很自豪。
chapter 29:解释器模式和模板方法模式的区别
对比项
解释器模式
模板方法模式
核心目的
解释表达式语言
固定算法流程
扩展方式
新增表达式节点
子类重写步骤
使用场景
规则表达式、公式
固定流程、导入模板
结构
表达式树
继承结构
示例
解释优惠规则
固定文件导入流程
解释器模式处理的是“语言”。
模板方法处理的是“流程”。
chapter 30:解释器模式和组合模式的关系 解释器模式经常会用到组合模式。
因为表达式树本身就是一种树形结构。
例如:
1 2 3 AndExpression ├── GreaterThanOrEqualExpression └── EqualExpression
AndExpression 包含两个子表达式。
这很像组合模式:
1 2 private final Expression left;private final Expression right;
所以可以说:
解释器模式常常使用组合模式来表示语法树。
但两者目的不同。
组合模式关注树形结构统一处理。
解释器模式关注解释某种语言或表达式。
chapter 31:解释器模式的优点 1. 规则表达能力强 可以用表达式描述复杂业务条件。
2. 规则可配置 规则可以存数据库或配置中心,不一定写死在代码里。
3. 扩展语法比较清晰 新增一种操作符,可以新增一个表达式类。
4. 符合开闭原则的一部分 新增表达式类型时,可以不改已有表达式类。
5. 适合简单 DSL 可以为业务定义一套小语言,提高表达力。
6. 表达式树结构清晰 复杂规则可以拆成多个表达式节点。
chapter 32:解释器模式的缺点 1. 类数量容易增加 每种语法规则可能都需要一个表达式类。
例如:
GreaterThanExpression
LessThanExpression
EqualExpression
AndExpression
OrExpression
NotExpression
InExpression
BetweenExpression
类会越来越多。
2. 解析器实现复杂 真正难的往往不是表达式类,而是解析字符串规则。
尤其是支持括号、优先级、函数、字符串转义时。
3. 性能可能不高 表达式解释通常比直接代码执行慢。
可以通过缓存表达式树优化。
4. 不适合复杂语言 如果语言复杂,应该考虑成熟解析器或脚本引擎。
5. 调试成本较高 规则是字符串时,错误可能运行时才暴露。
例如:
变量名写错了,编译器不会帮你发现。
chapter 33:适用场景 解释器模式适合以下场景。
1. 业务规则可以表达成简单语言 例如:
1 amount >= 100 AND userLevel == VIP
2. 规则需要动态配置 规则不适合写死在代码里。
3. 文法比较稳定 操作符和语法结构不会频繁大改。
4. 表达式复杂度可控 规则不是特别复杂,不需要完整脚本语言能力。
5. 需要解释执行 例如优惠规则、权限规则、风控规则、公式计算。
chapter 34:不适合使用的场景 以下场景不建议使用解释器模式。
1. 规则非常简单 只有一两个 if else,没必要解释器模式。
2. 语法非常复杂 如果要支持完整 SQL、JavaScript、正则、复杂函数,最好用成熟工具。
3. 性能要求极高 频繁解释复杂表达式可能有性能压力。
4. 规则不需要动态配置 如果规则长期固定,策略模式或普通代码更清晰。
5. 团队缺少解析器经验 解释器模式涉及文法、解析、表达式树,理解成本不低。
chapter 35:真实项目中的实践建议 1. 先确认是否真的需要 DSL 不要为了设计模式而设计模式。
如果策略模式能解决,就别上解释器。
2. 语法要简单 业务 DSL 最怕越来越像编程语言。
最开始可能只是:
后来加:
1 IF amount > 100 THEN discount = 10 ELSE discount = 0
再后来加函数、循环、变量赋值、时间计算。
最后你会发现自己在写一门新语言。
谨慎。
3. 表达式解析结果要缓存 不要每次都解析字符串。
1 cache.getOrParse(ruleId, rule)
这是非常必要的优化。
4. 变量名要做白名单 不要允许任意变量名。
可以定义支持变量:
1 2 3 4 5 amount userLevel productCategory city userTag
否则配置人员写错变量,运行时才发现。
5. 错误提示要友好 规则配置错误时,要明确提示:
1 2 3 Unsupported operator: <> Variable not found: amount Invalid number: abc
不要只抛一个 RuntimeException。
6. 不要自己解析复杂表达式 如果要支持括号、优先级、函数、字符串转义、多类型比较,建议考虑成熟框架。
7. 解释器要和业务边界隔离 不要让解释器直接访问数据库、调用远程服务。
解释器应该只根据 Context 解释表达式。
需要的数据提前放进 Context。
8. 注意安全问题 如果使用脚本引擎或 SpEL,要限制可调用方法,避免安全风险。
例如不要让规则表达式执行任意 Java 方法。
9. 规则要有版本管理 业务规则经常变,建议保存:
规则 ID;
规则版本;
规则表达式;
创建人;
更新时间;
是否启用;
变更记录。
chapter 36:完整案例代码汇总 上下文 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 import java.math.BigDecimal;import java.util.HashMap;import java.util.Map;public class RuleContext { private final Map<String, Object> variables = new HashMap <>(); public void put (String name, Object value) { variables.put(name, value); } public Object get (String name) { return variables.get(name); } public BigDecimal getBigDecimal (String name) { Object value = variables.get(name); if (value == null ) { throw new IllegalArgumentException ("Variable not found: " + name); } if (value instanceof BigDecimal decimal) { return decimal; } if (value instanceof Number number) { return BigDecimal.valueOf(number.doubleValue()); } return new BigDecimal (value.toString()); } public String getString (String name) { Object value = variables.get(name); if (value == null ) { throw new IllegalArgumentException ("Variable not found: " + name); } return value.toString(); } }
表达式接口 1 2 3 4 public interface Expression { boolean interpret (RuleContext context) ; }
大于等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class GreaterThanOrEqualExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public GreaterThanOrEqualExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) >= 0 ; } }
小于等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import java.math.BigDecimal;public class LessThanOrEqualExpression implements Expression { private final String variableName; private final BigDecimal expectedValue; public LessThanOrEqualExpression (String variableName, BigDecimal expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { BigDecimal actualValue = context.getBigDecimal(variableName); return actualValue.compareTo(expectedValue) <= 0 ; } }
等于表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class EqualExpression implements Expression { private final String variableName; private final String expectedValue; public EqualExpression (String variableName, String expectedValue) { this .variableName = variableName; this .expectedValue = expectedValue; } @Override public boolean interpret (RuleContext context) { String actualValue = context.getString(variableName); return expectedValue.equals(actualValue); } }
AND 表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class AndExpression implements Expression { private final Expression left; private final Expression right; public AndExpression (Expression left, Expression right) { this .left = left; this .right = right; } @Override public boolean interpret (RuleContext context) { return left.interpret(context) && right.interpret(context); } }
OR 表达式 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class OrExpression implements Expression { private final Expression left; private final Expression right; public OrExpression (Expression left, Expression right) { this .left = left; this .right = right; } @Override public boolean interpret (RuleContext context) { return left.interpret(context) || right.interpret(context); } }
简单解析器 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 import java.math.BigDecimal;public class SimpleRuleParser { public Expression parse (String rule) { if (rule == null || rule.isBlank()) { throw new IllegalArgumentException ("rule can not be blank" ); } if (rule.contains(" AND " )) { String[] parts = rule.split(" AND " , 2 ); return new AndExpression ( parse(parts[0 ]), parse(parts[1 ]) ); } if (rule.contains(" OR " )) { String[] parts = rule.split(" OR " , 2 ); return new OrExpression ( parse(parts[0 ]), parse(parts[1 ]) ); } return parseSingleExpression(rule); } private Expression parseSingleExpression (String rule) { if (rule.contains(">=" )) { String[] parts = rule.split(">=" , 2 ); return new GreaterThanOrEqualExpression ( parts[0 ].trim(), new BigDecimal (parts[1 ].trim()) ); } if (rule.contains("<=" )) { String[] parts = rule.split("<=" , 2 ); return new LessThanOrEqualExpression ( parts[0 ].trim(), new BigDecimal (parts[1 ].trim()) ); } if (rule.contains("==" )) { String[] parts = rule.split("==" , 2 ); return new EqualExpression ( parts[0 ].trim(), parts[1 ].trim() ); } throw new IllegalArgumentException ("Unsupported rule expression: " + rule); } }
表达式缓存 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import java.util.Map;import java.util.concurrent.ConcurrentHashMap;public class RuleExpressionCache { private final SimpleRuleParser parser = new SimpleRuleParser (); private final Map<Long, Expression> cache = new ConcurrentHashMap <>(); public Expression getOrParse (Long ruleId, String rule) { return cache.computeIfAbsent(ruleId, id -> parser.parse(rule)); } public void remove (Long ruleId) { cache.remove(ruleId); } public void clear () { cache.clear(); } }
优惠券对象 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public class Coupon { private final Long couponId; private final String name; private final String ruleExpression; public Coupon (Long couponId, String name, String ruleExpression) { this .couponId = couponId; this .name = name; this .ruleExpression = ruleExpression; } public Long getCouponId () { return couponId; } public String getName () { return name; } public String getRuleExpression () { return ruleExpression; } }
优惠券匹配器 1 2 3 4 5 6 7 8 9 10 11 12 13 public class CouponMatcher { private final RuleExpressionCache expressionCache = new RuleExpressionCache (); public boolean match (Coupon coupon, RuleContext context) { Expression expression = expressionCache.getOrParse( coupon.getCouponId(), coupon.getRuleExpression() ); return expression.interpret(context); } }
客户端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 import java.math.BigDecimal;import java.util.List;public class CouponMatcherDemo { public static void main (String[] args) { List<Coupon> coupons = List.of( new Coupon (1L , "满 100 VIP 专享券" , "amount >= 100 AND userLevel == VIP" ), new Coupon (2L , "满 200 图书券" , "amount >= 200 AND productCategory == BOOK" ), new Coupon (3L , "普通满 50 券" , "amount >= 50" ) ); RuleContext context = new RuleContext (); context.put("amount" , new BigDecimal ("150" )); context.put("userLevel" , "VIP" ); context.put("productCategory" , "DIGITAL" ); CouponMatcher matcher = new CouponMatcher (); for (Coupon coupon : coupons) { boolean matched = matcher.match(coupon, context); System.out.println("优惠券:" + coupon.getName() + ",是否可用:" + matched); } } }
chapter 37:一句话总结 解释器模式的本质是:
为一类小语言或规则表达式定义文法,并用表达式对象解释执行这些规则。
它适合:
优惠规则;
权限规则;
风控规则;
报表公式;
工作流条件;
简单 DSL;
可配置业务表达式。
解释器模式最重要的不是写几个 Expression 类,而是判断:
你的业务规则是否真的适合被表达成一套小语言。
如果规则简单,直接 if else。
如果规则种类有限,使用策略模式。
如果规则流程明确,使用责任链模式。
如果规则需要动态配置、组合表达、解释执行,才考虑解释器模式。
好的解释器模式像一套清晰的业务语法:让规则可读、可配、可扩展。
坏的解释器模式像自制编程语言:开始只是优惠规则,最后变成“业务 JavaScript”,谁维护谁沉默。
参考资料
Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides. Design Patterns: Elements of Reusable Object-Oriented Software .
Robert C. Martin. Agile Software Development, Principles, Patterns, and Practices .
Martin Fowler. Domain-Specific Languages .
Terence Parr. The Definitive ANTLR 4 Reference .
Spring Framework Documentation: Spring Expression Language.
Refactoring Guru: Interpreter Pattern.
SourceMaking: Interpreter Design Pattern.
启示录 富贵岂由人,时会高志须酬。
能成功于千载者,必以近察远。