Interpreter 解释器模式实战解读:基于 java-design-patterns 的语法树构建与表达式求值
发布时间:2026/10/1 8:08:28来源:尧图网络
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载导读本文以开源仓库 java-design-patterns 中的 Interpreter解释器模式实现为骨架完整讲解该行为型模式的核心理念、类层次设计与后缀表达式求值实战。读者将掌握如何为一个简单语言如基础四则运算定义语法表示抽象语法树并用栈式求值器逐 token 解释句子同时了解该模式在 Java 标准库中的真实应用场景及其适用边界。模式目的为语言定义语法表示与解释器给定一种语言为它的文法grammar定义一个表示同时提供一个利用该表示来解释语言中句子的解释器。这是 Gang of Four 对 Interpreter 模式的经典定义。它属于行为型模式核心思路是语言中的每个符号终结符或非终结符对应一个类一句话的语法树是该模式Composite的一个实例客户端通过它求值解释整句话。在仓库中该模式的分类信息可在 localization/es/interpreter/README.md 的 front matter 中看到category: Behavioraltag 含 Gang of Four。现实例子与通俗解释现实例子半身人小孩在学校学习基础数学从最简单的算式开始1 1、4 - 2、5 5 等。我们需要一个能读懂并计算出这些算式结果的小计算器。通俗解释解释器模式负责解释目标语言中的句子——把4 3 2 - 1 *这样的字符串翻译成由表达式对象组成的语法树再递归求值得到最终结果。Wikipedia 定义解释器模式是一种指定如何评估语言中语句的设计模式。基本思想是让专门语言的每个符号终结符或非终结符对应一个类语言中语句的语法树是组合模式Composite的一个实例用于为客户端评估解释该语句。类层次设计从抽象 Expression 到具体表达式抽象基类 Expression要实现基础数学运算的解释首先需要一套表达式层级。其抽象基类是Expression声明了求值入口interpret()与便于调试和日志输出的toString()public abstract class Expression { public abstract int interpret(); Override public abstract String toString(); }对应源码位于 Expression.java。整个解释器体系都建立在这两个抽象方法之上interpret()递归求值toString()返回运算符符号如、-、*用于日志中标识当前操作。终结符NumberExpression最简单的一类表达式是数字表达式内部只保存一个整数属于语法树中的叶子节点终结符public class NumberExpression extends Expression { private final int number; public NumberExpression(int number) { this.number number; } public NumberExpression(String s) { this.number Integer.parseInt(s); } Override public int interpret() { return number; } Override public String toString() { return number; } }源码见 NumberExpression.java。它提供了两个构造器一个直接接收int另一个接收字符串并通过Integer.parseInt(s)完成 token 到数值的转换——这正是解析阶段把4这类字符串变成可求值对象的关键。非终结符PlusExpression / MinusExpression / MultiplyExpression更复杂的表达式是运算类如PlusExpression、MinusExpression和MultiplyExpression。它们各持有一个左子表达式和一个右子表达式构成语法树的内部节点非终结符。以加法为例public class PlusExpression extends Expression { private final Expression leftExpression; private final Expression rightExpression; public PlusExpression(Expression leftExpression, Expression rightExpression) { this.leftExpression leftExpression; this.rightExpression rightExpression; } Override public int interpret() { return leftExpression.interpret() rightExpression.interpret(); } Override public String toString() { return ; } }对应源码 PlusExpression.java。其余两个运算类结构完全相同仅在interpret()中把换成-与*可分别参考 MinusExpression.java 与 MultiplyExpression.java。可以清楚看到模式的精髓求值逻辑被递归地下放到每个节点自身——PlusExpression.interpret()只负责把左右两棵子树的结果相加它不关心子树内部是什么。这正是为每个符号建立一个类的直接体现。程序化示例用栈解析后缀表达式仓库的入口程序 App.java 演示了完整的解析与求值流程。App.java的类注释明确说明本示例支持前缀prefix、中缀infix和后缀postfix三种记号法示例采用后缀法——运算符跟在操作数之后。public static void main(String[] args) { // the halfling kids are learning some basic math at school // define the math string we want to parse final var tokenString 4 3 2 - 1 *; // the stack holds the parsed expressions var stack new StackExpression(); // tokenize the string and go through them one by one var tokenList tokenString.split( ); for (var s : tokenList) { if (isOperator(s)) { // when an operator is encountered we expect that the numbers can be popped from the top of // the stack var rightExpression stack.pop(); var leftExpression stack.pop(); LOGGER.info(popped from stack left: {} right: {}, leftExpression.interpret(), rightExpression.interpret()); var operator getOperatorInstance(s, leftExpression, rightExpression); LOGGER.info(operator: {}, operator); var result operator.interpret(); // the operation result is pushed on top of the stack var resultExpression new NumberExpression(result); stack.push(resultExpression); LOGGER.info(push result to stack: {}, resultExpression.interpret()); } else { // numbers are pushed on top of the stack var i new NumberExpression(s); stack.push(i); LOGGER.info(push to stack: {}, i.interpret()); } } // in the end, the final result lies on top of the stack LOGGER.info(result: {}, stack.pop().interpret()); }两个关键辅助方法App.java还提供了解析流程的两个支撑方法均为public static便于测试直接调用isOperator(String s)判断 token 是否为运算符当前仅识别、-、*三种public static boolean isOperator(String s) { return s.equals() || s.equals(-) || s.equals(*); }getOperatorInstance(String s, Expression left, Expression right)根据运算符字符串返回对应的表达式实例使用 Java 14 的switch箭头语法arrow syntax简洁分派public static Expression getOperatorInstance(String s, Expression left, Expression right) { return switch (s) { case - new PlusExpression(left, right); case - - new MinusExpression(left, right); default - new MultiplyExpression(left, right); }; }栈式求值算法逐步推导以tokenString 4 3 2 - 1 *为例整个流程遵循经典后缀表达式求值算法遇到数字压栈遇到运算符则弹出栈顶两个表达式、构建运算节点并求值再把结果作为新的NumberExpression压回栈顶。逐 token 推演如下步骤当前 token操作栈内容自底向上14非运算符 → 压栈[4]23非运算符 → 压栈[4, 3]32非运算符 → 压栈[4, 3, 2]4-弹出 3、2 → 3 - 2 1 → 结果压栈[4, 1]51非运算符 → 压栈[4, 1, 1]6弹出 1、1 → 1 1 2 → 结果压栈[4, 2]7*弹出 4、2 → 4 × 2 8 → 结果压栈[8]8结束stack.pop().interpret()最终结果8注意步骤 4 的弹出顺序栈顶先弹出的是rightExpression右操作数再弹出leftExpression左操作数因此减法得到3 - 2 1而非2 - 3保证了减法以及除法等不可交换运算的语义正确性。运行输出程序执行后的控制台输出对应原文档popped from stack left: 1 right: 1 operator: push result to stack: 2 popped from stack left: 4 right: 2 operator: * push result to stack: 8 result: 8结合仓库 README.md 中的完整运行记录可以更直观地看到每一步的日志顺序含时间戳与类名前缀push to stack: 4 push to stack: 3 push to stack: 2 popped from stack left: 3 right: 2 operator: - push result to stack: 1 push to stack: 1 popped from stack left: 1 right: 1 operator: push result to stack: 2 popped from stack left: 4 right: 2 operator: * push result to stack: 8 result: 8两次输出相互印证4 3 2 - 1 *对应的中缀语义是4 * ((3 - 2) 1)最终结果为 8。日志中的operator: 正是各运算表达式toString()的返回值体现了toString()在调试信息中的用途。类图下面是该模式在仓库中的类图来源 interpreter/etc/interpreter_1.png类图清晰展示了模式的核心结构Expression为抽象根NumberExpression为终结符叶子三个二元运算表达式为组合型非终结符节点语法树整体构成一个 Composite 结构。测试佐证表达式求值的正确性验证仓库为每个表达式类都提供了对应的单元测试从源码层面印证了实现行为的正确性ExpressionTest.java 是测试基类用ParameterizedTest参数化测试驱动求值验证NumberExpressionTest.java、PlusExpressionTest.java、MinusExpressionTest.java、MultiplyExpressionTest.java 分别为各类的测试实现。ExpressionTest的prepareParameters(IntBinaryOperator resultCalc)方法会为两个操作数生成-10 到 10 区间内全部 20 × 20 400 组组合并通过传入的二元运算函数如(a, b) - a b计算期望结果然后断言factory.apply(first, second).interpret()与期望值相等。同时它还验证每个表达式的toString()返回值与预期符号一致例如加法应为。这一设计既验证了interpret()的数值正确性也验证了toString()的符号契约可作为读者扩展新运算类如除法、取模时的测试模板。适用性何时使用 Interpreter 模式存在需要解释的语言且语言中的语句可以表示为抽象语法树时应考虑使用解释器模式。该模式在以下场景中效果最佳文法足够简单。对于复杂文法为文法建立的类层级会变得庞大而难以管理此时诸如解析器生成器parser generator之类的工具是更好的选择——它们无需构建抽象语法树即可解释表达式可以节省空间乃至时间。效率不是关键考量。最高效的解释器通常不是直接解释语法分析树而是先将树翻译成另一种形式再执行。例如正则表达式常常被转换为状态机。但即便如此翻译器本身仍可用解释器模式实现因此该模式依然适用。已知用途Java 标准库中的解释器模式解释器模式在 Java 生态中有大量成熟应用以下为仓库文档列出的知名实例java.util.Pattern正则表达式的编译与匹配把正则文法解释为可执行的匹配状态机java.text.NormalizerUnicode 文本规范化java.text.Format的全部子类如SimpleDateFormat、MessageFormat等解释日期/消息格式文法javax.el.ELResolver统一表达式语言Unified EL的解析与求值。此外各类数据库管理系统中的 SQL 解析器也是该模式的典型应用领域。相关模式与优缺点相关模式Composite组合模式两者常常协同使用。解释器模式借助组合模式把文法表示为树形结构——Expression的递归interpret()调用正是树形组合的典型遍历方式本仓库示例本身就是组合结构的最佳演示Flyweight享元模式适用于共享状态以减少内存占用特别是解释器中存在大量重复元素如频繁出现的数字 token时。优点在不修改文法或数据类的前提下容易新增解释表达式的操作文法直接用语言本身实现易于修改与扩展。权衡对大型文法而言可能变得复杂且低效文法中的每条规则对应一个类复杂文法会导致类数量激增。小结本文以 java-design-patterns 仓库的 interpreter 模块为完整案例还原了 Interpreter 模式从文法定义Expression层级、token 解析App.java的栈式后缀求值到测试验证ExpressionTest参数化测试的全链路实现。该实现可作为读者在自研 DSL、规则引擎、计算器或查询解析器等场景中落地解释器模式的直接参考源码与测试均可从 interpreter 模块 深入查阅。参考资料本文核心内容整理自 localization/es/interpreter/README.md实现细节取自 interpreter 模块源码与测试模式理论依据来自经典著作《Design Patterns: Elements of Reusable Object-Oriented Software》《Head First Design Patterns》与《Refactoring to Patterns》。赞分享示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载相关推荐Java 设计模式之解释器模式Interpreter用 java-design-patterns 构建自定义算术表达式解析器Java 设计模式之解释器模式Interpreter用 java design patterns 构建自定义算术表达式解析器 解释器模式Interpre示例工程教程DesignPatternsPHP 解释器模式Interpreter Pattern实战用 PHP 8 类构建二进制逻辑表达式语法树DesignPatternsPHP 解释器模式Interpreter Pattern实战用 PHP 8 类构建二进制逻辑表达式语法树 解释器模式Inte示例工程教程Java 设计模式实战Composite组合模式构建部分-整体树形结构java-design-patterns 仓库解读Java 设计模式实战Composite组合模式构建部分 整体树形结构java design patterns 仓库解读 本篇技术指南以 java d示例工程教程上一篇Eclipse Mosquitto版本升级指南从1.6到2.0迁移步骤下一篇Processing.py终极入门指南用Python轻松开启创意编程之旅创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
网站建设高端定制企业官网