解释器模式:让代码学会“读”和“算”

解释器模式,这个名字听起来挺唬人的。其实说白了,就是给程序装上一颗“理解语言”的脑子。你给它一段表达式,比如 3 + 5 * 2,它能自己拆解、计算,最后告诉你答案是 13。

我在做报表系统的时候,遇到过这样的需求:用户想自定义计算公式,比如 销售额 * 0.8 - 成本。总不能每次用户改公式都改代码吧?这时候解释器模式就派上用场了。

模式核心:语法树与上下文

解释器模式的核心思想,是把“语言”的每一条规则,都变成一个类。比如数字是一个类,加法是一个类,乘法是一个类。这些类组合起来,就形成了一棵“抽象语法树”(AST)。

你想想看,这就像搭积木。每个积木块只知道自己该干什么,组合起来就能完成复杂的计算。

核心角色:

  • 抽象表达式(AbstractExpression): 定义解释操作的接口,通常是一个 interpret(context) 方法。
  • 终结符表达式(TerminalExpression): 语法中的最小单元,比如数字、变量。它自己就能解释自己。
  • 非终结符表达式(NonterminalExpression): 由多个子表达式组合而成,比如加法、乘法。它需要递归调用子表达式的解释方法。
  • 上下文(Context): 存放全局信息,比如变量的值。

SVG:解释器模式结构图

AbstractExpression + interpret(Context) TerminalExpression + interpret(Context) 例如:数字、变量 NonterminalExpression + interpret(Context) 例如:加法、乘法 Context 变量值映射表 递归

实战:四则运算表达式计算器

咱们直接上代码。我习惯用 Java 来演示,因为它的接口和抽象类用起来很顺手。

先定义抽象表达式接口:

public interface Expression {
    int interpret(Context ctx);
}

然后是终结符表达式——数字:

public class NumberExpression implements Expression {
    private int number;

    public NumberExpression(int number) {
        this.number = number;
    }

    @Override
    public int interpret(Context ctx) {
        return number;
    }
}

非终结符表达式——加法:

public 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 ctx) {
        return left.interpret(ctx) + right.interpret(ctx);
    }
}

减法、乘法、除法同理,我就不一一写了。你想想看,每个运算符都是一个类,它们只关心自己的计算逻辑,多清爽。

避坑指南:我曾经踩过的坑

我曾经在实现变量表达式时,忘了处理变量未定义的情况。结果用户输入了一个不存在的变量名,程序直接抛 NullPointerException。后来我养成了习惯:在 interpret() 方法里先检查变量是否存在,不存在就抛一个语义清晰的异常。

还有一个坑:递归深度。如果表达式嵌套太深,比如 1 + (2 + (3 + (4 + ...))),Java 的栈可能会溢出。我建议在解析阶段就限制最大嵌套层数,或者改用迭代的方式处理。

SQL 解析器设计思路

解释器模式在 SQL 解析里用得很多。一条 SELECT name FROM users WHERE age > 18,可以拆解成:

  • SelectExpression: 代表整个 SELECT 语句
  • FromExpression: 代表 FROM 子句
  • WhereExpression: 代表 WHERE 子句
  • ComparisonExpression: 代表比较条件,比如 age > 18

每个子句都是一个非终结符表达式,它们组合起来就能完整地解释一条 SQL。我在做数据中台项目时,就用这个思路实现了一个简易的 SQL 解析器,支持基本的查询和过滤。

小提示: 解释器模式虽然灵活,但不要滥用。如果语法规则太多(比如超过 20 条),类的数量会爆炸,维护起来很痛苦。这时候可以考虑用 ANTLR 或 JavaCC 这样的解析器生成器。

优缺点总结

优点 缺点
易于扩展:加一种运算就加一个类 类数量膨胀:每条规则一个类
语法规则清晰:每个类只负责一种规则 递归深度可能导致栈溢出
适合实现领域特定语言(DSL) 复杂语法解析效率较低

我个人觉得,解释器模式最适合的场景是:语法规则相对固定、数量不多,但需要频繁扩展的领域。比如自定义公式计算、简单的脚本语言、配置文件解析等。

嗯,今天就聊到这儿。记住一句话:解释器模式,就是把“语言”变成“代码”。


公众号:蓝海资料掘金营,微信deep3321