想自己写个“规则引擎”?你得先学会解释器模式

你是否也曾深陷在解析自定义规则或命令的泥潭,为了处理一个类似 (A and B) or C 的简单查询,你的 Spring Boot 代码里充斥着复杂的字符串分割和层层嵌套的 if-else,难以维护和扩展?是时候用解释器设计模式 (Interpreter Design Pattern) 来解脱了!这是一种行为型设计模式,它为一种语言定义一种文法表示,并提供一个解释器来处理这种文法。

在 Spring Boot 中,当你需要构建一个自定义的迷你查询语言、规则引擎或配置解析器时,解释器模式能帮你创建出结构清晰、易于扩展的系统,把你的特定领域语言(DSL)处理逻辑变成一个优雅的对象树。本文将探讨为什么临时的字符串解析会损害你的代码库,通过一个实际的搜索查询示例来展示解释器模式的强大威力,并一步步指导你如何运用其思想 —— 让我们今天就开始解锁构建自定义语言的优雅之道吧!

什么是解释器设计模式?

解释器模式的核心思想是:为一个语言的文法规则建立一个对象表示(即抽象语法树 - AST),然后通过解释这个对象树来执行相应的逻辑。

简单来说,就是把一个“句子”(如查询字符串)转换成一个树形结构的对象,树上的每个节点都代表一个语法元素(如操作数、操作符),然后调用树根节点的“解释”方法,就能得到整个句子的执行结果。

这个模式的核心组件通常包括:

  • • 抽象表达式 (AbstractExpression): 定义一个 interpret() 方法,这是语法树中所有节点的公共接口。

  • • 终结符表达式 (TerminalExpression): 代表语法中的“终结符”(如数字、变量)。它是树的叶子节点。

  • • 非终结符表达式 (NonTerminalExpression): 代表语法中的“非终结符”(如加、减、与、或等操作符)。它通常会包含其他表达式(子节点),形成树的枝干。

  • • 上下文 (Context): 包含解释器之外的一些全局信息。

  • • 客户端 (Client): 负责构建(或通过一个解析器构建)代表特定句子的抽象语法树。

为什么要在 Spring Boot 中使用解释器模式?

理解和运用解释器模式的思想能带来诸多好处:

  • • 封装语法规则 (Encapsulates Grammar): 每条语法规则都可以被一个独立的类表示,使得整个语言的结构清晰,易于理解和维护。

  • • 易于扩展 (Extensibility): 当需要给语言增加新的规则(例如,增加一个新的操作符)时,通常只需要增加一个新的表达式类即可,符合开闭原则。

  • • 逻辑清晰 (Clear Logic): 通过将句子解析成一个树形结构,复杂的执行逻辑被分解到各个节点上,每个节点职责单一,易于调试。

  • • 与其它模式协作 (Cooperates with Other Patterns): 解释器模式构建的抽象语法树本身就是一个组合模式 (Composite Pattern) 的完美应用。同时,我们可以使用访问者模式 (Visitor Pattern) 来对这棵树执行多种完全不同的操作(如求值、打印、类型检查等)。

  • • 构建强大的自定义功能 (Builds Powerful Features): 在Spring Boot应用中,你可以用它来构建自定义的、对业务人员友好的规则引擎、搜索引擎查询语法等。

问题所在:混乱的字符串解析

假设你需要实现一个简单的计算器,处理类似 "10 + 5 - 3" 的字符串。

你可能会忍不住这样写:

public intcalculate(String expression) {
    String[] parts = expression.split(" ");
    intresult= Integer.parseInt(parts[0]);
    for (inti=1; i < parts.length; i += 2) {
        Stringoperator= parts[i];
        intnumber= Integer.parseInt(parts[i + 1]);
        if (operator.equals("+")) {
            result += number;
        } elseif (operator.equals("-")) {
            result -= number;
        }
    }
    return result;
}

这种代码在最简单的情况下似乎可行,但一旦需求变得复杂(例如,需要支持 */ 和括号 ()),这个方法就会迅速变成一堆难以维护的 if-else 和复杂的循环。

❌ 难以扩展: 增加新的运算符或语法规则(如括号)几乎需要重写整个方法。
❌ 代码脆弱: 对输入的格式非常敏感,稍微不规范的输入(如多个空格)就可能导致程序崩溃。
❌ 逻辑混乱: 解析逻辑和计算逻辑混杂在一起。

✅ 解释器模式来修复
解释器模式将 "10 + 5 - 3" 这样的字符串,转换成一个对象树,例如 new Subtract(new Add(new Number(10), new Number(5)), new Number(3))。然后,我们只需调用树根的 interpret() 方法,即可得到结果。

一步步实现 Java 示例:简单数学解释器

第一步:定义抽象表达式接口

// Context可以用来传递全局信息,这里为了简化,我们省略了它
public interface Expression {
    int interpret();
}

第二步:创建终结符和非终结符表达式

// 终结符
classNumberimplementsExpression {
    privateint number;
    publicNumber(int number) { this.number = number; }
    @Overridepublicintinterpret() { return number; }
}

// 非终结符
classPlusimplementsExpression {
    private Expression left, right;
    publicPlus(Expression left, Expression right) { this.left = left; this.right = right; }
    @Overridepublicintinterpret() { return left.interpret() + right.interpret(); }
}

classMinusimplementsExpression {
    private Expression left, right;
    publicMinus(Expression left, Expression right) { this.left = left; this.right = right; }
    @Overridepublicintinterpret() { return left.interpret() - right.interpret(); }
}

第三步:客户端构建抽象语法树(AST)并使用
在真实场景中,构建AST的过程由一个专门的解析器完成。这里我们手动构建来演示。

import java.util.Stack;

publicclassMain {
    publicstaticvoidmain(String[] args) {
        // 假设解析器已经将 "10 + 5 - 3" 解析并构建了如下的树
        Expressionexpression=newMinus(
            newPlus(newNumber(10), newNumber(5)),
            newNumber(3)
        );

        // 客户端只需调用 interpret 方法即可
        intresult= expression.interpret();
        System.out.println("表达式 '10 + 5 - 3' 的计算结果是: " + result); // 输出 12
    }
}
Spring Boot 应用案例:自定义商品筛选规则

假设我们需要实现一个商品筛选功能,支持类似 (brand:nike AND price<100) 这样的查询字符串。

第一步:定义表达式接口和上下文

// 上下文,这里是我们要筛选的单个商品
class Product { /* 包含 brand, price等属性 */ }

// 表达式接口
interface ProductExpression {
    boolean interpret(Product context);
}

第二步:实现各种表达式作为普通Java类

// 终结符表达式
classBrandExpressionimplementsProductExpression { /* ... */ }
classPriceExpressionimplementsProductExpression { /* ... */ }

// 非终结符表达式
classAndExpressionimplementsProductExpression {
    private ProductExpression expr1;
    private ProductExpression expr2;
    publicAndExpression(ProductExpression e1, ProductExpression e2) { expr1 = e1; expr2 = e2; }
    @Overridepublicbooleaninterpret(Product context) {
        return expr1.interpret(context) && expr2.interpret(context);
    }
}
classOrExpressionimplementsProductExpression { /* ... */ }

第三步:将解析器和搜索服务作为 Spring Bean

import org.springframework.stereotype.Component;

// 解析器,负责将查询字符串转换为表达式树
@Component
publicclassQueryParser {
    public ProductExpression parse(String query) {
        // 此处是复杂的解析逻辑,可能用到栈、递归下降等算法
        // 为了演示,我们手动构建一个
        // 对应 "(brand:nike AND price<100)"
        returnnewAndExpression(newBrandExpression("nike"), newPriceExpression("<", 100));
    }
}

@Service
publicclassProductSearchService {
    privatefinal QueryParser parser;

    publicProductSearchService(QueryParser parser) {
        this.parser = parser;
    }

    public List search(String query, List allProducts) {
        // 1. 使用解析器构建表达式树
        ProductExpressionexpr= parser.parse(query);
        
        // 2. 遍历所有商品,用表达式树进行筛选
        return allProducts.stream()
                .filter(product -> expr.interpret(product))
                .collect(Collectors.toList());
    }
}

客户端(如Controller)只需调用 ProductSearchService.search(...) 即可,完全无需关心查询是如何被解析和执行的。

解释器模式 vs. 组合模式 & 访问者模式
  • • 解释器 vs. 组合: 解释器模式构建的抽象语法树就是一个组合模式的实例。组合模式的意图是让客户端可以统一地处理单个对象和组合对象,而解释器模式的意图是定义一种语言并解释它。可以说,解释器模式是组合模式的一个特定应用场景。

  • • 解释器 vs. 访问者: 访问者模式用于在不修改对象结构的前提下,为其增加新的操作。你可以用一个访问者来“访问”解释器构建好的语法树,从而实现多种功能,例如:SQLExportVisitor(将查询转换为SQL语句),PrettyPrintVisitor(格式化打印查询语句)。它们是黄金搭档。

✅ 何时使用解释器模式
  • • 当有一个简单的语言需要解释执行,并且你可以将该语言的句子表示为一个抽象语法树时。

  • • 当你需要处理一些固定文法的、重复出现的问题时(如各种自定义配置文件、规则引擎)。

  • • 当语言的文法相对稳定,扩展主要是增加新的操作时(可以配合访问者模式)。

何时不宜使用解释器模式
  • • 当文法非常复杂时: 维护大量的表达式类会变得非常困难。此时,应该使用专业的解析器生成工具(如 ANTLR, JavaCC)。

  • • 当对性能要求极高时: 解析和构建语法树有一定开销,对于性能敏感的应用,可能需要更底层的实现。

  • • 当你只是需要解析简单的键值对或格式化字符串时,有更简单直接的方法。

总结

解释器设计模式是一种功能强大但应用场景相对特定的模式。它通过将语言文法映射为类层次结构,为我们提供了一种极其优雅的方式来处理自定义的迷你语言或规则。

虽然它不常出现在日常的CRUD业务中,但其思想是构建编译器、规则引擎、模板引擎等复杂系统的基石。在现代化的 Spring Boot 开发中,当你需要为你的应用赋予一种可由用户自定义的、动态的业务规则能力时,解释器模式将为你打开一扇通往灵活和强大功能的大门。理解它,意味着你具备了从“使用”语言到“创造”语言的更高阶的抽象能力。

你可能感兴趣的:(Spring,boot,解释器模式)