设计模式:解释器模式

欢迎你来读这篇博客,这篇博客主要是关于解释器模式
其中包括解释器模式的核心思想、适用场景、文法表达式、终结符表达式、非终结符表达式、优缺点、与策略模式/责任链模式/规则引擎的区别,以及 Java 后端开发中订单优惠规则表达式解释器的完整案例。

序言

解释器模式是 23 种设计模式里相对“冷门”的一个。

很多人学设计模式时看到它,第一反应可能是:

这东西是不是在写编译器?

这个理解不完全错。

解释器模式确实和“语言”“文法”“表达式解析”有关。

但它不一定非要写一个完整编程语言。

在 Java 后端开发里,我们也经常会遇到一些“小语言”或“小表达式”:

  • 优惠规则表达式;
  • 权限规则表达式;
  • 数据过滤表达式;
  • 报表计算公式;
  • 工作流条件表达式;
  • 风控规则表达式;
  • 搜索查询表达式;
  • 配置中心条件表达式。

比如电商系统里有一条优惠规则:

1
amount >= 100 AND userLevel == VIP

它表示:

订单金额大于等于 100,并且用户等级是 VIP,才可以使用这个优惠。

如果规则很少,直接写 if else 就行。

但如果规则很多,而且需要动态配置,就不能每次新增规则都改代码、发版。

这时就可以把规则写成表达式,然后由程序解释执行。

解释器模式解决的就是这类问题:

为某种语言定义文法,并实现一个解释器来解释这种语言中的句子。

说得简单点:

解释器模式就是让程序读懂你定义的一套规则表达式。

当然,它不是万能钥匙。

如果表达式复杂到像 SQL、JavaScript、正则表达式、复杂规则引擎,那就别自己硬写解释器了。能用成熟框架就用成熟框架,别拿青春给 parser 填坑。

正文

chapter 1:什么是解释器模式

解释器模式,英文是 Interpreter Pattern,属于行为型设计模式。

它的定义是:

给定一种语言,定义它的文法表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

这句话听起来很学术。

可以拆成三层:

  1. 你定义一种“小语言”;
  2. 你规定这种语言怎么写,也就是文法;
  3. 你写代码解释这种语言,并执行对应逻辑。

例如:

1
amount >= 100 AND userLevel == VIP

这就是一条规则语言。

它的文法大概包含:

  • 比较表达式:amount >= 100
  • 等值表达式:userLevel == VIP
  • 逻辑表达式:AND
  • 变量:amountuserLevel
  • 常量:100VIP

解释器要做的是:

根据当前上下文,判断这条表达式是否成立。

例如上下文是:

1
2
amount = 150
userLevel = VIP

解释结果就是:

1
true

如果上下文是:

1
2
amount = 80
userLevel = VIP

解释结果就是:

1
false

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. 终结符表达式

终结符表达式就是不能再拆的基本表达式。

例如:

1
amount >= 100

它是一个基础条件。

再比如:

1
userLevel == VIP

它也是一个基础条件。

这些可以看作终结符表达式。

2. 非终结符表达式

非终结符表达式是由其他表达式组合而成的表达式。

例如:

1
amount >= 100 AND userLevel == VIP

它由两个基础表达式通过 AND 组合而成。

这里的 AND 表达式就是非终结符表达式。

再比如:

1
(amount >= 100 AND userLevel == VIP) OR couponType == NEW_USER

这是更复杂的非终结符表达式。

3. 简单类比

可以这样理解:

1
2
终结符:积木块
非终结符:由积木块搭出来的结构

解释器模式就是把规则拆成一棵表达式树,然后递归解释这棵树。

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 后端场景来讲解释器模式。

假设我们正在做一个优惠券系统。

优惠券可以配置使用规则。

例如:

1
amount >= 100

表示订单金额满 100 可用。

1
userLevel == VIP

表示用户等级是 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);
}

所有表达式都实现这个接口。

每个表达式都能根据上下文解释出一个布尔结果。

例如:

1
amount >= 100

结果可能是:

1
true

也可能是:

1
false

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;
}
}

这个表达式对应:

1
amount >= 100

其中:

  • 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;
}
}

它对应:

1
amount <= 500

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);
}
}

它对应:

1
userLevel == VIP

或者:

1
productCategory == BOOK

这是一个终结符表达式。

因为它不能再继续拆成更小的业务表达式。

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
left AND right

例如:

1
amount >= 100 AND userLevel == VIP

这里:

  • leftamount >= 100
  • rightuserLevel == 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
left OR right

例如:

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);
}
}

输出:

1
规则是否匹配:true

如果把 amount 改成 80

1
context.put("amount", new BigDecimal("80"));

结果就是:

1
false

这就是解释器模式最核心的执行方式:

表达式对象组成表达式树,递归解释上下文。

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. 解释左表达式;
  2. 解释右表达式;
  3. 对结果做 && 运算。

如果规则是:

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 表达式

如果要支持:

1
NOT userLevel == VIP

可以增加一个 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
amount >= 100

符合:

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);

如果规则变更,可以删除缓存:

1
cache.remove(couponId);

真实项目中还要考虑:

  • 多实例缓存刷新;
  • 配置中心变更;
  • 数据库规则更新;
  • 本地缓存过期;
  • 表达式版本号;
  • 规则灰度发布。

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. 调试成本较高

规则是字符串时,错误可能运行时才暴露。

例如:

1
amunt >= 100

变量名写错了,编译器不会帮你发现。

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
amount >= 100

后来加:

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.

启示录

富贵岂由人,时会高志须酬。

能成功于千载者,必以近察远。


设计模式:解释器模式
https://allendericdalexander.github.io/2026/04/01/java/design/16interpreter-pattern-blog/
作者
AtLuoFu
发布于
2026年4月1日
许可协议