Appearance
解释器模式 (Interpreter)
一、定义
一句话概括:定义一种语言的文法规则,并创建一个解释器来解释该语言中的句子。
官方定义:Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.
二、解决的问题
2.1 问题场景
当需要频繁解析和执行某种特定语法规则时,例如:
- 数学表达式计算:
(a + b) * c - SQL 查询解析
- 正则表达式匹配
- 自定义规则引擎:
age > 18 AND score >= 60 - 配置文件解析
如果为每种语法都写一套专门的解析代码,代码会非常冗余且难以维护。
2.2 不用解释器模式会怎样?
java
// 反例:硬编码解析逻辑
public int evaluate(String expr) {
if (expr.contains("+")) {
String[] parts = expr.split("\\+");
return Integer.parseInt(parts[0]) + Integer.parseInt(parts[1]);
}
// 如果增加减乘除,代码会越来越复杂
return 0;
}问题:语法规则硬编码,扩展新规则需要修改大量代码,不易维护。
三、结构
3.1 角色组成
| 角色 | 说明 |
|---|---|
| AbstractExpression(抽象表达式) | 定义解释操作的接口 |
| TerminalExpression(终结符表达式) | 实现与文法终结符相关的解释操作 |
| NonTerminalExpression(非终结符表达式) | 实现与非终结符相关的解释操作,通常包含子表达式 |
| Context(上下文) | 包含解释器之外的全局信息,如变量值 |
| Client(客户端) | 构建抽象语法树(AST),调用解释操作 |
3.2 类图(ASCII)
┌──────────────────┐
│ AbstractExpr │
├──────────────────┤
│ + interpret(ctx) │
└────────┬─────────┘
│
┌─────┴──────┐
│ │
▼ ▼
┌────────┐ ┌──────────┐
│Terminal│ │NonTerminal│
│Expr │ │Expr │
├────────┤ ├──────────┤
│value │ │left, right│
│ │ │ │
└────────┘ └──────────┘四、代码实现
4.1 基础实现
java
// ==================== 上下文 ====================
class Context {
private Map<String, Integer> variables = new HashMap<>();
public void assign(String name, int value) {
variables.put(name, value);
}
public int lookup(String name) {
return variables.get(name);
}
}
// ==================== 抽象表达式 ====================
interface Expression {
int interpret(Context context);
}
// ==================== 终结符表达式:变量 ====================
class VariableExpression implements Expression {
private String name;
public VariableExpression(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
return context.lookup(name);
}
}
// ==================== 终结符表达式:常量 ====================
class ConstantExpression implements Expression {
private int value;
public ConstantExpression(int value) {
this.value = value;
}
@Override
public int interpret(Context context) {
return value;
}
}
// ==================== 非终结符表达式:加法 ====================
class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) + right.interpret(context);
}
}
// ==================== 非终结符表达式:减法 ====================
class SubtractExpression implements Expression {
private Expression left;
private Expression right;
public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) - right.interpret(context);
}
}
// ==================== 非终结符表达式:乘法 ====================
class MultiplyExpression implements Expression {
private Expression left;
private Expression right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
return left.interpret(context) * right.interpret(context);
}
}
// ==================== 非终结符表达式:除法 ====================
class DivideExpression implements Expression {
private Expression left;
private Expression right;
public DivideExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
int divisor = right.interpret(context);
if (divisor == 0) throw new ArithmeticException("除数不能为0");
return left.interpret(context) / divisor;
}
}
// ==================== 客户端 ====================
public class InterpreterDemo {
public static void main(String[] args) {
Context ctx = new Context();
ctx.assign("a", 10);
ctx.assign("b", 20);
ctx.assign("c", 5);
// 构建表达式: (a + b) * c
Expression expr = new MultiplyExpression(
new AddExpression(
new VariableExpression("a"),
new VariableExpression("b")
),
new VariableExpression("c")
);
// 解释执行: (10 + 20) * 5 = 150
System.out.println("结果:" + expr.interpret(ctx));
}
}4.2 进阶实现
4.2.1 先序遍历 AST
java
// 抽象语法树打印器
class ASTPrinter {
public static String print(Expression expr) {
if (expr instanceof ConstantExpression) {
return String.valueOf(((ConstantExpression) expr).interpret(null));
} else if (expr instanceof VariableExpression) {
return "var";
} else if (expr instanceof AddExpression) {
return "(+ " + printField(expr, "left") + " " + printField(expr, "right") + ")";
} else if (expr instanceof MultiplyExpression) {
return "(* " + printField(expr, "left") + " " + printField(expr, "right") + ")";
}
return expr.toString();
}
private static String printField(Expression expr, String fieldName) {
try {
java.lang.reflect.Field field = expr.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
return print((Expression) field.get(expr));
} catch (Exception e) {
return "?";
}
}
}4.2.2 简单规则引擎
java
// 规则引擎:age > 18 AND score >= 60
interface RuleExpression {
boolean interpret(Map<String, Object> context);
}
class GreaterThanExpression implements RuleExpression {
private String key;
private int threshold;
public GreaterThanExpression(String key, int threshold) {
this.key = key;
this.threshold = threshold;
}
@Override
public boolean interpret(Map<String, Object> context) {
return (int) context.get(key) > threshold;
}
}
class GreaterEqualExpression implements RuleExpression {
private String key;
private int threshold;
public GreaterEqualExpression(String key, int threshold) {
this.key = key;
this.threshold = threshold;
}
@Override
public boolean interpret(Map<String, Object> context) {
return (int) context.get(key) >= threshold;
}
}
class AndExpression implements RuleExpression {
private RuleExpression left;
private RuleExpression right;
public AndExpression(RuleExpression left, RuleExpression right) {
this.left = left;
this.right = right;
}
@Override
public boolean interpret(Map<String, Object> context) {
return left.interpret(context) && right.interpret(context);
}
}
class OrExpression implements RuleExpression {
private RuleExpression left;
private RuleExpression right;
public OrExpression(RuleExpression left, RuleExpression right) {
this.left = left;
this.right = right;
}
@Override
public boolean interpret(Map<String, Object> context) {
return left.interpret(context) || right.interpret(context);
}
}
// 使用
public class RuleEngineDemo {
public static void main(String[] args) {
// age > 18 AND score >= 60
RuleExpression rule = new AndExpression(
new GreaterThanExpression("age", 18),
new GreaterEqualExpression("score", 60)
);
Map<String, Object> ctx1 = new HashMap<>();
ctx1.put("age", 20);
ctx1.put("score", 85);
System.out.println("合格:" + rule.interpret(ctx1)); // true
Map<String, Object> ctx2 = new HashMap<>();
ctx2.put("age", 16);
ctx2.put("score", 85);
System.out.println("合格:" + rule.interpret(ctx2)); // false
}
}4.2.3 Spring EL 表达式
java
// Spring Expression Language(SpEL)是解释器模式的经典应用
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
public class SpELDemo {
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
// 简单表达式
System.out.println(parser.parseExpression("'Hello ' + 'World'").getValue());
// 数学运算
System.out.println(parser.parseExpression("100 * 2 + 400").getValue());
// 上下文变量
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("price", 100);
context.setVariable("discount", 0.8);
System.out.println(parser.parseExpression("#price * #discount").getValue(context));
// 方法调用
User user = new User("张三", 25);
System.out.println(parser.parseExpression("name").getValue(user));
System.out.println(parser.parseExpression("name.length()").getValue(user));
System.out.println(parser.parseExpression("age > 18").getValue(user));
}
}
class User {
private String name;
private int age;
public User(String name, int age) { this.name = name; this.age = age; }
public String getName() { return name; }
public int getAge() { return age; }
}4.3 生产级实现
Spring Boot 动态规则引擎
java
// ==================== 规则表达式解析器 ====================
interface RuleNode {
boolean evaluate(Map<String, Object> context);
}
// 叶子节点:比较规则
class ComparisonNode implements RuleNode {
private String field;
private String operator;
private Object value;
public ComparisonNode(String field, String operator, Object value) {
this.field = field;
this.operator = operator;
this.value = value;
}
@Override
public boolean evaluate(Map<String, Object> context) {
Object actual = context.get(field);
if (actual == null) return false;
if (actual instanceof Number && value instanceof Number) {
double a = ((Number) actual).doubleValue();
double v = ((Number) value).doubleValue();
switch (operator) {
case ">": return a > v;
case ">=": return a >= v;
case "<": return a < v;
case "<=": return a <= v;
case "==": return a == v;
case "!=": return a != v;
}
} else if (actual instanceof String) {
switch (operator) {
case "==": return actual.equals(value);
case "!=": return !actual.equals(value);
case "contains": return actual.toString().contains(value.toString());
}
}
return false;
}
}
// 组合节点:AND/OR
class LogicalNode implements RuleNode {
private String logic; // AND, OR
private List<RuleNode> children = new ArrayList<>();
public LogicalNode(String logic) {
this.logic = logic;
}
public void addChild(RuleNode child) {
children.add(child);
}
@Override
public boolean evaluate(Map<String, Object> context) {
if ("AND".equals(logic)) {
return children.stream().allMatch(c -> c.evaluate(context));
} else if ("OR".equals(logic)) {
return children.stream().anyMatch(c -> c.evaluate(context));
}
return false;
}
}
// ==================== 规则服务 ====================
@Service
class RuleEngineService {
private final Map<String, RuleNode> ruleCache = new ConcurrentHashMap<>();
public void registerRule(String ruleId, RuleNode rule) {
ruleCache.put(ruleId, rule);
}
public boolean evaluate(String ruleId, Map<String, Object> context) {
RuleNode rule = ruleCache.get(ruleId);
if (rule == null) throw new IllegalArgumentException("规则不存在:" + ruleId);
return rule.evaluate(context);
}
}
// ==================== 使用示例 ====================
@RestController
@RequestMapping("/api/rules")
class RuleController {
@Autowired private RuleEngineService ruleEngine;
@PostConstruct
public void init() {
// 注册促销规则:金额 > 1000 AND 等级 == VIP
LogicalNode rule = new LogicalNode("AND");
rule.addChild(new ComparisonNode("amount", ">", 1000));
rule.addChild(new ComparisonNode("level", "==", "VIP"));
ruleEngine.registerRule("promotion_check", rule);
}
@PostMapping("/evaluate")
public Map<String, Object> evaluate(@RequestBody Map<String, Object> context) {
boolean result = ruleEngine.evaluate("promotion_check", context);
return Map.of("eligible", result);
}
}五、优缺点
优点
- 易于扩展:新增语法规则只需新增表达式类,符合开闭原则
- 易于实现语法:每个语法规则对应一个类,结构与语法树一一对应
- 灵活性高:可以自由组合表达式构建复杂的语法树
缺点
- 类膨胀:每个语法规则都需要一个类,复杂语法会产生大量类
- 效率较低:递归调用解释器,对于复杂语法性能较差
- 适用场景有限:只有固定且简单的语法才适合使用解释器模式
- 难以处理复杂语法:对于复杂语法,应使用解析器生成器(如 ANTLR)
六、适用场景
- 表达式求值:数学表达式、逻辑表达式计算器
- 规则引擎:业务规则动态配置和执行
- SQL 解析:ORM 框架中的 SQL 语句解析
- 正则表达式:正则引擎的内部实现
- 配置文件解析:特定格式的配置文件解析
- 模板引擎:渲染模板时的表达式解析
七、JDK / Spring 框架中的实际应用
| 框架 | 应用位置 | 说明 |
|---|---|---|
| JDK | java.util.regex.Pattern | 正则表达式解释器 |
| Spring | SpelExpressionParser | Spring EL 表达式解析 |
| Spring | @Value 注解 | 使用 SpEL 解析属性值 |
| Spring Security | @PreAuthorize | 使用 SpEL 解析权限表达式 |
| MyBatis | 动态 SQL | XML 中的 <if>, <foreach> 等标签解析 |
| Thymeleaf | 模板引擎 | 表达式解析 |
八、与其他模式的关系
与组合模式
- 解释器模式的抽象语法树(AST)是组合模式的应用,非终结符表达式是组合节点,终结符表达式是叶子节点
与享元模式
- 解释器模式中的终结符表达式可以使用享元模式共享,减少对象创建
与访问者模式
- 如果需要在不修改表达式类的情况下增加新的操作(如类型检查、优化),可以使用访问者模式
九、面试常见问题
Q1:解释器模式有什么局限性?
A:解释器模式适用于语法简单的场景。当语法规则过多时,类数量会爆炸式增长,且递归调用解释器效率较低。对于复杂语法,应使用 ANTLR、JavaCC 等解析器生成工具。
Q2:解释器模式和组合模式有什么关系?
A:解释器模式中的抽象语法树本质上是组合模式。非终结符表达式是 Composite 节点,终结符表达式是 Leaf 节点。解释器模式在组合模式的基础上增加了 interpret() 操作。
Q3:Spring 的 SpEL 是如何使用解释器模式的?
A:SpEL 将表达式字符串解析为 AST,然后调用 getValue() 递归求值。SpelExpressionParser 是解析器,SpelExpression 是表达式对象,StandardEvaluationContext 是上下文。整个过程是典型的解释器模式。
Q4:什么时候应该使用解释器模式,什么时候应该使用脚本引擎?
A:当语法规则固定且简单时,使用解释器模式;当需要动态执行复杂的业务逻辑时,应使用 Groovy、JavaScript 等脚本引擎(如 ScriptEngine),它们更灵活但性能开销更大。
