设计模式-解释器模式

文章目录

  • 一、概述
    • [1.1 结构与角色](#1.1 结构与角色)
    • [1.2 适用场景](#1.2 适用场景)
  • 二、实现方式
    • [2.1 基础实现](#2.1 基础实现)
    • [2.2 带变量支持的增强实现](#2.2 带变量支持的增强实现)
    • [2.3 扩展:简易规则引擎](#2.3 扩展:简易规则引擎)
  • [三、JDK 源码中的解释器](#三、JDK 源码中的解释器)
    • [3.1 java.util.regex.Pattern ------ 正则表达式](#3.1 java.util.regex.Pattern —— 正则表达式)
    • [3.2 java.text.Format ------ 格式化器](#3.2 java.text.Format —— 格式化器)
    • [3.3 javax.el.Expression ------ EL 表达式](#3.3 javax.el.Expression —— EL 表达式)
  • 四、总结

一、概述

在软件开发中,经常会遇到这样的场景:需要解析和执行一种特定的小型语言表达式规则 。例如,计算器需要解析 1 + 2 * 3 这样的数学表达式;SQL 引擎需要解析 SELECT * FROM user WHERE age > 18 这样的查询语句;机器人需要解析"前进 10 步,左转 90 度"这样的指令序列。如果不使用设计模式,通常的做法是为每种语法规则写一堆 if-else 来硬编码解析逻辑,导致代码臃肿、扩展困难、可读性差

java 复制代码
// 反例:用 if-else 硬编码解析数学表达式------难以扩展
public int evaluate(String expr) {
    if (expr.contains("+")) {
        String[] parts = expr.split("\\+");
        return Integer.parseInt(parts[0].trim()) + Integer.parseInt(parts[1].trim());
    } else if (expr.contains("-")) {
        String[] parts = expr.split("-");
        return Integer.parseInt(parts[0].trim()) - Integer.parseInt(parts[1].trim());
    } else if (expr.contains("*")) {
        // ...越来越多的分支,且无法处理优先级和嵌套
    }
    // 每新增一种运算符或语法规则,就要修改这个方法
    return 0;
}

解释器模式(Interpreter Pattern)正是为了解决这个问题而诞生的------它定义一种语言的文法表示,并提供一个解释器来解释该语言中的句子 。解释器模式将每种语法规则封装成一个独立的表达式类,通过组合这些表达式构建出抽象语法树(AST),然后递归解释执行。

生活中的解释器模式例子:

  • 计算器1 + 2 * 3 被解析成一棵语法树------乘法节点优先于加法节点,递归计算得出结果 7
  • 正则表达式引擎\d{3}-\d{4} 被解析为一组字符模式匹配规则,逐字符解释执行匹配
  • 乐谱演奏:乐谱上的音符(do re mi fa so la si)就是"语言",演奏者根据乐谱符号解释执行出对应的音高
  • 摩尔斯电码 :将 · · · --- --- --- · · · 解释为 SOS,每个点划组合对应一个字母
  • 编程语言编译器:Java 源码被解析成 AST,编译器遍历 AST 生成字节码------解释器模式的终极应用

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

1.1 结构与角色

解释器模式包含以下角色:
构建
根节点
实现
实现
持有子表达式
interpret(context)
Client 客户端
Context 上下文
AbstractSyntaxTree 抽象语法树
AbstractExpression 抽象表达式
TerminalExpression 终结符表达式
NonTerminalExpression 非终结符表达式

  • AbstractExpression(抽象表达式) :声明一个抽象的 interpret() 解释操作,所有表达式节点都实现该接口
  • TerminalExpression(终结符表达式) :实现与文法中的终结符相关的解释操作。终结符是文法的最小单元,不可再分(如数学表达式中的数字、变量)
  • NonTerminalExpression(非终结符表达式) :实现与非终结符相关的解释操作。非终结符由终结符或其他非终结符组合而成(如加法表达式 A + B,其中 A 和 B 本身也是表达式)
  • Context(上下文):包含解释器之外的全局信息,存储变量的值、函数定义等,在解释过程中传递
  • Client(客户端):根据文法构建抽象语法树,创建上下文对象,调用解释操作

关键概念------终结符与非终结符 :终结符是文法中不可再分的原子符号(如数字 3、变量 x),非终结符由其他符号递归组合而成(如 Add(A, B) 由两个子表达式组成)。终结符表达式是树的叶子节点,非终结符表达式是树的内部节点。

1.2 适用场景

  • 需要解释一种简单的、文法规则明确的小型语言或表达式
  • 文法较为简单,语法规则的数量不太多(复杂度高时不适用)
  • 效率不是最关键的因素(递归解释效率较低,频繁调用时需考虑性能)
  • 需要将文法规则与解释逻辑分离,方便新增语法规则
  • 典型的应用场景:正则表达式、数学表达式求值、SQL 解析、规则引擎、机器人指令

二、实现方式

解释器模式的核心实现思路是:将每种语法规则封装为一个表达式类,通过组合构建抽象语法树,然后递归调用 interpret() 方法完成解释执行。

以"数学表达式求值"为例,支持加减乘除四种运算,正确处理运算符优先级(乘除优先于加减):
构建表达式树
实现
实现
实现
实现
实现
持有
持有
持有
持有
客户端
Context 上下文
Expression 抽象表达式
NumberExpression 数字 终结符
AddExpression 加法 非终结符
SubExpression 减法 非终结符
MulExpression 乘法 非终结符
DivExpression 除法 非终结符

对于表达式 1 + 2 * 3,构建出的抽象语法树如下:
Add (+)
1
Mul (*)
2
3

2.1 基础实现

(1)抽象表达式

java 复制代码
/**
 * 抽象表达式:所有表达式节点的公共接口
 * 声明 interpret() 解释操作方法
 */
public interface Expression {

    /**
     * 解释执行当前表达式
     *
     * @param context 上下文(可存储变量/中间结果)
     * @return 计算结果
     */
    int interpret();
}

(2)终结符表达式------数字

java 复制代码
/**
 * 终结符表达式:数字
 * 文法的最小单元,不可再分
 * 解释操作:直接返回数字本身的值
 */
public class NumberExpression implements Expression {

    private final int value;

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

    @Override
    public int interpret() {
        return value;
    }
}

(3)非终结符表达式------加法

java 复制代码
/**
 * 非终结符表达式:加法
 * 由两个子表达式(左、右操作数)组合而成
 */
public class AddExpression implements Expression {

    private final Expression left;
    private final Expression right;

    public AddExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}

(4)非终结符表达式------减法

java 复制代码
/**
 * 非终结符表达式:减法
 */
public class SubExpression implements Expression {

    private final Expression left;
    private final Expression right;

    public SubExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}

(5)非终结符表达式------乘法

java 复制代码
/**
 * 非终结符表达式:乘法
 */
public class MulExpression implements Expression {

    private final Expression left;
    private final Expression right;

    public MulExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}

(6)非终结符表达式------除法

java 复制代码
/**
 * 非终结符表达式:除法
 */
public class DivExpression implements Expression {

    private final Expression left;
    private final Expression right;

    public DivExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpret() {
        int divisor = right.interpret();
        if (divisor == 0) {
            throw new ArithmeticException("除数不能为零");
        }
        return left.interpret() / divisor;
    }
}

(7)客户端------构建语法树并求值

java 复制代码
public class InterpreterDemo {
    public static void main(String[] args) {
        // 表达式:1 + 2 * 3
        // 构建 AST:
        //      Add
        //     /   \
        //    1    Mul
        //         / \
        //        2   3
        Expression expr = new AddExpression(
                new NumberExpression(1),
                new MulExpression(
                        new NumberExpression(2),
                        new NumberExpression(3)
                )
        );

        System.out.println("1 + 2 * 3 = " + expr.interpret());
        // 输出:1 + 2 * 3 = 7

        // 表达式:10 - 4 / 2 + 5
        // 构建 AST:
        //        Add
        //       /   \
        //     Sub    5
        //    /   \
        //  10    Div
        //        / \
        //       4   2
        Expression expr2 = new AddExpression(
                new SubExpression(
                        new NumberExpression(10),
                        new DivExpression(
                                new NumberExpression(4),
                                new NumberExpression(2)
                        )
                ),
                new NumberExpression(5)
        );

        System.out.println("10 - 4 / 2 + 5 = " + expr2.interpret());
        // 输出:10 - 4 / 2 + 5 = 13
    }
}

运行结果:

复制代码
1 + 2 * 3 = 7
10 - 4 / 2 + 5 = 13

关键点 :客户端手动词法/语法分析构建 AST,将"1 + 2 * 3"这种中缀表达式转换为树结构。加法节点持有左操作数 1 和右操作数(乘法子树),乘法子树再持有 2 和 3。解释执行时递归遍历树,先计算乘法(2 * 3 = 6),再计算加法(1 + 6 = 7),自然地处理了运算符优先级

2.2 带变量支持的增强实现

实际场景中往往需要支持变量。以"布尔表达式求值"为例,支持 ANDORNOT 逻辑运算以及变量取值:
构建
实现
实现
实现
实现
实现
持有
持有
持有
客户端
Context (变量表)
BoolExpression 抽象布尔表达式
VariableExpression 变量 终结符
ConstantExpression 常量 终结符
AndExpression AND 非终结符
OrExpression OR 非终结符
NotExpression NOT 非终结符

(1)上下文------存储变量值

java 复制代码
import java.util.HashMap;
import java.util.Map;

/**
 * 上下文:存储变量的取值
 * 解释器在解释变量表达式时,从此处查找变量的值
 */
public class Context {

    private final Map<String, Boolean> variables = new HashMap<>();

    /**
     * 设置变量值
     *
     * @param name  变量名
     * @param value 布尔值
     */
    public void assign(String name, boolean value) {
        variables.put(name, value);
    }

    /**
     * 获取变量值
     *
     * @param name 变量名
     * @return 布尔值,变量不存在时返回 false
     */
    public boolean lookup(String name) {
        return variables.getOrDefault(name, false);
    }
}

(2)抽象表达式------布尔表达式

java 复制代码
/**
 * 抽象表达式:布尔表达式
 */
public interface BoolExpression {

    /**
     * 解释执行布尔表达式
     *
     * @param context 上下文(包含变量表)
     * @return 计算结果
     */
    boolean interpret(Context context);
}

(3)终结符表达式------变量

java 复制代码
/**
 * 终结符表达式:变量
 * 解释操作:从上下文中查找变量值
 */
public class VariableExpression implements BoolExpression {

    private final String name;

    public VariableExpression(String name) {
        this.name = name;
    }

    @Override
    public boolean interpret(Context context) {
        return context.lookup(name);
    }
}

(4)终结符表达式------布尔常量

java 复制代码
/**
 * 终结符表达式:布尔常量
 * 解释操作:直接返回常量值
 */
public class ConstantExpression implements BoolExpression {

    private final boolean value;

    public ConstantExpression(boolean value) {
        this.value = value;
    }

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

(5)非终结符表达式------AND 运算

java 复制代码
/**
 * 非终结符表达式:逻辑与(AND)
 */
public class AndExpression implements BoolExpression {

    private final BoolExpression left;
    private final BoolExpression right;

    public AndExpression(BoolExpression left, BoolExpression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public boolean interpret(Context context) {
        return left.interpret(context) && right.interpret(context);
    }
}

(6)非终结符表达式------OR 运算

java 复制代码
/**
 * 非终结符表达式:逻辑或(OR)
 */
public class OrExpression implements BoolExpression {

    private final BoolExpression left;
    private final BoolExpression right;

    public OrExpression(BoolExpression left, BoolExpression right) {
        this.left = left;
        this.right = right;
    }

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

(7)非终结符表达式------NOT 运算

java 复制代码
/**
 * 非终结符表达式:逻辑非(NOT)
 */
public class NotExpression implements BoolExpression {

    private final BoolExpression expression;

    public NotExpression(BoolExpression expression) {
        this.expression = expression;
    }

    @Override
    public boolean interpret(Context context) {
        return !expression.interpret(context);
    }
}

(8)客户端------构建条件规则并求值

java 复制代码
public class BooleanInterpreterDemo {
    public static void main(String[] args) {
        // 创建上下文,设置变量值
        Context context = new Context();
        context.assign("A", true);
        context.assign("B", false);
        context.assign("C", true);

        // 构建表达式:(A AND NOT B) OR C
        // AST:
        //        OR
        //       /  \
        //     AND   C
        //    /   \
        //   A    NOT
        //         |
        //         B
        BoolExpression expr = new OrExpression(
                new AndExpression(
                        new VariableExpression("A"),
                        new NotExpression(new VariableExpression("B"))
                ),
                new VariableExpression("C")
        );

        boolean result = expr.interpret(context);
        System.out.println("(A AND NOT B) OR C = " + result);
        // A=true, B=false, C=true
        // NOT B = true → A AND true = true → true OR true = true
        // 输出:(A AND NOT B) OR C = true

        // 修改变量 B 为 true,重新求值
        context.assign("B", true);
        System.out.println("修改 B=true 后,(A AND NOT B) OR C = "
                + expr.interpret(context));
        // NOT B = false → A AND false = false → false OR true = true
        // 输出:修改 B=true 后,(A AND NOT B) OR C = true
    }
}

设计要点 :上下文 Context 将变量与表达式解耦------表达式不直接持有变量值,而是通过上下文查找。修改变量值后同样的表达式树可以得出不同的结果,这体现了规则引擎的核心思想:规则(表达式树)不变,数据(上下文)可变

2.3 扩展:简易规则引擎

利用解释器模式可以构建一个简易的规则引擎。以"用户权限判定"为例,规则为:(年龄 >= 18 AND 会员) OR 管理员

java 复制代码
public class RuleEngineDemo {
    public static void main(String[] args) {
        // 规则:(age >= 18 AND isVip) OR isAdmin

        // 用户1:25岁,会员,非管理员
        Context user1 = new Context();
        user1.assign("ageGte18", true);
        user1.assign("isVip", true);
        user1.assign("isAdmin", false);

        BoolExpression rule = new OrExpression(
                new AndExpression(
                        new VariableExpression("ageGte18"),
                        new VariableExpression("isVip")
                ),
                new VariableExpression("isAdmin")
        );

        System.out.println("用户1 有无权限?" + rule.interpret(user1));
        // true AND true → true; true OR false → true
        // 输出:用户1 有无权限?true

        // 用户2:16岁,非会员,非管理员
        Context user2 = new Context();
        user2.assign("ageGte18", false);
        user2.assign("isVip", false);
        user2.assign("isAdmin", false);

        System.out.println("用户2 有无权限?" + rule.interpret(user2));
        // false AND false → false; false OR false → false
        // 输出:用户2 有无权限?false
    }
}

三、JDK 源码中的解释器

JDK 本身就有多处解释器模式的经典应用。

3.1 java.util.regex.Pattern ------ 正则表达式

正则表达式是解释器模式最经典的 JDK 应用。Pattern 将正则表达式字符串编译成一棵内部的语法树,Matcher 逐字符执行匹配:

java 复制代码
import java.util.regex.Pattern;
import java.util.regex.Matcher;

public class RegexInterpreterDemo {
    public static void main(String[] args) {
        // Pattern.compile() 内部使用解释器模式解析正则语法
        Pattern pattern = Pattern.compile("\\d{3}-\\d{4}-\\d{4}");

        // Matcher 根据编译后的语法树执行匹配
        Matcher matcher = pattern.matcher("电话:138-1234-5678");
        if (matcher.find()) {
            System.out.println("匹配到手机号:" + matcher.group());
            // 输出:匹配到手机号:138-1234-5678
        }
    }
}

解析为正则AST
遍历AST匹配
Pattern.compile(regex)
抽象语法树
\d 数字节点
{3} 量词节点

  • 字符节点
    Matcher.matcher(input)
    逐个字符

Pattern.compile() 将正则表达式字符串编译成由节点(Node)组成的内部语法树------每个正则语法元素(\d{n}[a-z] 等)对应一个节点类,这正是解释器模式的核心思想。

3.2 java.text.Format ------ 格式化器

java.text.Format 系列类(DateFormatNumberFormatMessageFormat)使用了类似解释器模式的思想------将格式化模式字符串解析为内部结构,然后根据该结构对数据进行格式化或解析:

java 复制代码
import java.text.SimpleDateFormat;
import java.text.NumberFormat;
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Date;

public class FormatInterpreterDemo {
    public static void main(String[] args) throws ParseException {
        // SimpleDateFormat:将日期模式字符串解释为格式化/解析规则
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        String formatted = sdf.format(new Date());
        System.out.println("格式化日期:" + formatted);

        Date parsed = sdf.parse("2026-05-19 10:30:00");
        System.out.println("解析日期:" + parsed);

        // MessageFormat:将消息模式字符串解释为模板替换规则
        String template = "用户 {0} 在 {1,date,yyyy-MM-dd} 购买了 {2,number,#.##} 元的商品";
        String msg = MessageFormat.format(template, "张三", new Date(), 99.99);
        System.out.println(msg);
        // 输出:用户 张三 在 2026-05-19 购买了 99.99 元的商品
    }
}

SimpleDateFormat("yyyy-MM-dd HH:mm:ss") 中的 yyyyMMdd 就是"语言"的终结符,SimpleDateFormat 内部将其解析为对应的日期处理单元,这正是解释器模式的应用。

3.3 javax.el.Expression ------ EL 表达式

Java EE 中的表达式语言(EL,Expression Language)使用解释器模式来解析和执行 ${...}#{...} 表达式:

java 复制代码
import javax.el.ExpressionFactory;
import javax.el.ValueExpression;
import javax.el.ELContext;
import javax.el.StandardELContext;

public class ELInterpreterDemo {
    public static void main(String[] args) {
        ExpressionFactory factory = ExpressionFactory.newInstance();
        ELContext context = new StandardELContext(factory);

        // EL 表达式:计算算术表达式
        ValueExpression expr = factory.createValueExpression(
                context, "${1 + 2 * 3}", int.class);
        int result = (int) expr.getValue(context);
        System.out.println("EL 表达式 1 + 2 * 3 = " + result);
        // 输出:EL 表达式 1 + 2 * 3 = 7
    }
}

EL 表达式引擎内部将 ${1 + 2 * 3} 解析为 AST(数字节点 + 运算符节点),然后递归求值------这与我们前面实现的数学表达式解释器思路完全一致。


四、总结

解释器模式的核心思想是为语言定义文法,并为每种文法规则创建对应的表达式类,通过组合表达式对象构建抽象语法树(AST),递归解释执行。

优点:

  • 易于扩展:新增一种语法规则只需新增一个表达式类,无需修改已有类,符合开闭原则
  • 文法清晰:每种语法规则对应一个类,类的层次结构与文法规则一一对应,便于理解和维护
  • 易于实现:递归解释执行的逻辑天然适合树形结构的遍历,实现简单直观
  • 灵活组合:可以通过组合不同的表达式节点,灵活构建任意复杂的表达式

缺点:

  • 类数量膨胀:每条语法规则至少对应一个类,文法复杂时会产生大量表达式类,难以管理
  • 性能瓶颈:递归调用 interpret() 存在大量方法调用开销,且 AST 本身占用内存,不适合高性能场景
  • 适用面窄:仅适用于文法简单且固定的场景(通常不超过 20 条规则),复杂的语言需要专业的解析器生成工具(如 ANTLR、JavaCC)
  • 调试困难:递归嵌套的表达式树出问题时,定位错误节点比较麻烦

解释器模式 vs 组合模式:

维度 解释器模式 组合模式
目标 解析并执行特定语言的句子 将对象组织成树形结构表示整体-部分层次
核心操作 interpret() 递归解释执行 operation() 统一处理叶子和容器
节点类型 终结符 / 非终结符 叶子 / 容器(Composite)
典型场景 表达式求值、规则引擎、正则 文件系统、GUI 组件树、菜单

解释器模式与组合模式的关系:解释器模式中的 AST 本质上就是一棵组合树------终结符是叶子节点,非终结符是容器节点(持有子表达式)。解释器模式可以看作是组合模式在**特定领域(语言解析)**的深度应用。

适用场景:

  • 需要解释执行一种简单的、文法规则明确的语言或表达式
  • 文法较为简单(规则数量 < 20 条),且不频繁变化
  • 效率不是首要考虑因素
  • 需要将文法规则的定义与解释执行分离,方便单独修改规则

参考博客:

解释器模式 | 菜鸟教程:https://www.runoob.com/design-pattern/interpreter-pattern.html

相关推荐
Asurplus4 小时前
23中设计模式
设计模式·创建型·结构型·行为型
geovindu4 小时前
go: Semaphore Pattern
开发语言·后端·设计模式·golang·企业级信号量模式
写了20年代码的老程序员10 小时前
写了 20 年 Java,我发现 90% 的 if-null 和 try-catch 其实是因为缺了一条原则
设计模式·ai编程
货拉拉技术12 小时前
私域转化率翻倍的秘密:我们把多模态Agent融进了私域营销
人工智能·算法·设计模式
看山是山_Lau12 小时前
抽象工厂模式:一整套对象族如何统一创建?
设计模式·抽象工厂模式
木易 士心12 小时前
深入理解 OKHttp:设计模式、核心机制与架构优势
android·设计模式·架构
likerhood14 小时前
设计模式 · 外观模式(Facade Pattern)
设计模式·外观模式
++==14 小时前
设计模式:单例模式和观察者模式实现方式以及优化
观察者模式·单例模式·设计模式