Skip to content

解释器模式 (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);
    }
}

五、优缺点

优点

  1. 易于扩展:新增语法规则只需新增表达式类,符合开闭原则
  2. 易于实现语法:每个语法规则对应一个类,结构与语法树一一对应
  3. 灵活性高:可以自由组合表达式构建复杂的语法树

缺点

  1. 类膨胀:每个语法规则都需要一个类,复杂语法会产生大量类
  2. 效率较低:递归调用解释器,对于复杂语法性能较差
  3. 适用场景有限:只有固定且简单的语法才适合使用解释器模式
  4. 难以处理复杂语法:对于复杂语法,应使用解析器生成器(如 ANTLR)

六、适用场景

  1. 表达式求值:数学表达式、逻辑表达式计算器
  2. 规则引擎:业务规则动态配置和执行
  3. SQL 解析:ORM 框架中的 SQL 语句解析
  4. 正则表达式:正则引擎的内部实现
  5. 配置文件解析:特定格式的配置文件解析
  6. 模板引擎:渲染模板时的表达式解析

七、JDK / Spring 框架中的实际应用

框架应用位置说明
JDKjava.util.regex.Pattern正则表达式解释器
SpringSpelExpressionParserSpring EL 表达式解析
Spring@Value 注解使用 SpEL 解析属性值
Spring Security@PreAuthorize使用 SpEL 解析权限表达式
MyBatis动态 SQLXML 中的 <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),它们更灵活但性能开销更大。