← 返回题目列表

如何用 Java 实现解释器模式?

高频 中等 第 9 / 25 题 更新于 2026/07/28
解释器模式Java实现表达式

简化版

Java 实现解释器模式通常定义 Expression 接口和 interpret(Context) 方法,再实现变量表达式、常量表达式、与或非等组合表达式。客户端构建表达式树后,传入上下文解释得到结果。

详细版

示例:布尔规则表达式。

interface Expression {
    boolean interpret(Context context);
}

变量表达式:

class VariableExpression implements Expression {
    private String name;
    public boolean interpret(Context context) {
        return context.getBoolean(name);
    }
}

与表达式:

class AndExpression implements Expression {
    private Expression left;
    private Expression right;
    public boolean interpret(Context context) {
        return left.interpret(context) && right.interpret(context);
    }
}

客户端组合表达式树,再传入 Context 执行。

完整版教学

一、定义统一接口

统一接口是表达式树能递归组合的基础:

interface Expression {
    boolean interpret(Context context);
}

无论是变量、常量、与、或、非,都可以当成 Expression 使用。

二、实现终结符表达式

终结符通常不包含子表达式:

class BoolExpression implements Expression {
    private final boolean value;

    public boolean interpret(Context context) {
        return value;
    }
}

变量表达式则从 Context 中取值。

三、实现非终结符表达式

非终结符组合其他表达式:

class OrExpression implements Expression {
    private final Expression left;
    private final Expression right;

    public boolean interpret(Context context) {
        return left.interpret(context) || right.interpret(context);
    }
}

解释时会递归调用子表达式。

四、常见误区与工程判断

代码实现里最容易被忽略的是“字符串如何变成表达式树”。如果规则写死在代码里,示例很简单;如果规则来自配置,就需要解析器。

工程中表达式解析、校验、解释应该分层:解析负责语法,校验负责合法性,解释负责执行。混在一起会很难维护。

五、Java 示例要补上解析阶段的意识

Java 示例通常手动写 new AndExpression(a, b),这是为了展示模式结构。但工程里规则大多来自配置、数据库或用户输入,例如 "age > 18 && vip == true"。这时不能只写 Expression 类,还要有 parser 把字符串转换成表达式树。

实现时可以分层:Tokenizer 负责把字符串拆成 token,Parser 负责根据优先级和括号构建表达式树,Expression 负责解释执行,Context 负责提供变量值。这样每层职责清楚,出错时也容易定位是词法问题、语法问题还是运行时变量缺失。

如果面试只是要求写简单代码,可以写变量、常量、And、Or、Not 几个类;如果追问工程落地,就补充解析、异常处理、短路求值、类型校验和缓存表达式树。尤其是缓存很重要:同一条规则重复执行时,不应该每次都重新解析字符串。

六、用工程约束检验答案

Java 示例应区分构树与求值:new Add(new Number(1), new Multiply(new Number(2), new Number(3))) 的 interpret 结果必须为 7。若直接按字符串从左到右计算得到 9,说明解析阶段没有实现乘法优先级。

检查项核心判断工程含义
Expression统一返回类型 R让组合节点静态检查
不可变节点final 子表达式AST 可安全复用
Parsertoken 转 AST与 interpret 职责分离

把关键关系压缩成一条可复述的路径:

Expression<Integer> ast =
  Add(Number(1), Multiply(Number(2), Number(3)))
ast.interpret(context)
=> 1 + (2 * 3) = 7

演示代码手工 new AST 可以说明模式结构,但必须主动指出真实输入仍需要可靠 Parser。

落地前可以再按下面 3 步复核:

  1. 先说明“Expression”的核心机制:统一返回类型 R;再交代边界:让组合节点静态检查。
  2. 接着分析“不可变节点”:final 子表达式;不能遗漏对应代价或结果:AST 可安全复用。
  3. 最后用“Parser”检查方案:token 转 AST;验收时确认与 interpret 职责分离。

这三项构成完整判断链:先讲清Expression,再说明不可变节点,最后用Parser检验实现是否越界。

面试中若能给出违反“与 interpret 职责分离”的反例,再说明修正办法,答案就从模式定义落到了可验证的工程决策。

七、常见误区与追问

  • 误区:只看到“Expression”就认为方案成立。 必须同时说明核心机制“统一返回类型 R”和工程边界“让组合节点静态检查”。
  • 误区:把“不可变节点”当成无条件结论。 只有在“final 子表达式”成立时,才能据此讨论“AST 可安全复用”。
  • 追问:接口返回 Object 好吗? 灵活但丢失类型安全,单一结果域优先使用泛型。
  • 追问:节点为何推荐不可变? 不可变 AST 易缓存、共享和并发求值。
  • 追问:除零在哪里处理? DivideExpression 解释时检测并返回领域错误或抛出明确异常。
  • 追问:如何实现短路 AND? 先解释左节点,若为 false 就不解释右节点。
  • 追问:如何测试优先级?1+2*3=7(1+2)*3=9 等成对用例测试 Parser 与解释器。

八、加强记忆

记忆时抓住这条主线:Expression 统一解释接口;终结符处理基本单元;非终结符递归组合子表达式;真正工程难点常在解析表达式字符串。面试回答先给出模式意图,再用调用链或数据流说明角色协作,最后主动交代适用边界与工程代价。