首页 / 资讯中心 / 文章详情

Java 设计模式之解释器模式(Interpreter Pattern):为 Java 应用构建自定义解析器的完整实战指南

Java 设计模式之解释器模式(Interpreter Pattern):为 Java 应用构建自定义解析器的完整实战指南 ★ FEATURED ARTICLE
示例工程教程【免费下载链接】java-design-patternsDesign patterns implemented in Java项目地址https://gitcode.com/GitHub_Trending/ja/java-design-patterns点击查看免费下载导读本文以 java-design-patterns 仓库中的 Interpreter 模块 及其 中文文档 为核心系统讲解解释器模式Interpreter Pattern的核心思想、适用场景与源码级实现。读者将掌握如何用抽象语法树表示一种语言的文法、如何为每一种文法符号建立对应类并通过一个可运行的后缀表达式计算器示例4 3 2 - 1 *最终结果为 8理解从词法拆分、语法树构建到表达式求值的完整链路同时看到仓库测试对解释结果与运算符字符串表示的自动化验证方式。模式目的给定一种语言解释器模式要求定义其语法的表示形式以及使用该表示形式来解释该语言中句子的解释器。它是 Gang of Four 行为型模式之一。从仓库源码看这一思想被清晰地落在 Expression.java 这一抽象基类上它声明了所有表达式都必须实现的interpret()方法负责求值以及toString()方法负责返回该节点的运算符符号整个语法树就是由Expression及其子类组成的对象树。Interpreter 模式类图上图为本模式在仓库中的类图抽象类Expression是语法树节点的基础NumberExpression是终结符表达式叶子节点PlusExpression、MinusExpression、MultiplyExpression是非终结符表达式内部节点它们组合成复合结构对整棵语法树执行求值即完成对句子的解释。适用性当存在一种需要解释的语言且该语言中的语句可以表示为抽象语法树时请使用解释器模式。该模式在以下两种场景下效果最佳语法很简单。对于复杂语法为语法建立的类层次会变得庞大且难以管理。此时解析器生成器parser generator之类的工具是更好的选择——它们可以在不构建抽象语法树的情况下直接解释表达式从而节省空间、可能也节省时间。效率不是关键问题。通常最高效的解释器并不是通过直接解释解析树来实现的而是先把解析树转换成另一种形式。例如正则表达式通常会转换为状态机。不过即使如此翻译器本身依然可以通过解释器模式实现因此该模式仍然适用。仓库的 App.java 类注释中还补充了一条重要说明表达式可以使用**前缀prefix、中缀infix或后缀postfix记法来求值本示例采用后缀postfix**记法即运算符位于操作数之后。这也解释了为什么示例输入串是4 3 2 - 1 *而非日常习惯的4 * (3 - 2 1)。真实世界例子解释器模式在 Java 生态中有着广泛的实际应用仓库文档列出的典型代表包括java.util.Pattern——正则表达式编译与匹配的底层正是解释器的思想。java.text.Normalizer——Unicode 文本规范化的解释执行。java.text.Format的所有子类——SimpleDateFormat、DecimalFormat等格式解析器。javax.el.ELResolver——JavaEE 表达式语言EL的解析与求值。各类数据库管理系统中的 SQL 解析器。编程示例后缀算术表达式解释器在仓库中解释器模式被用来解析并计算一个简单算术语言。整个实现只包含 5 个核心类全部位于 interpreter/src/main/java/com/iluwatar/interpreter 下。为了支持基本数学运算我们需要一个表达式类层次。1. 抽象表达式文法树的基类public abstract class Expression { public abstract int interpret(); Override public abstract String toString(); }Expression是所有终结符数字与非终结符运算节点的共同父类interpret()是该语言的求值入口。2. 终结符表达式数字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; } }注意它提供了两个构造函数直接传入int用于解释器把中间结果回填到栈上或传入String并通过Integer.parseInt(s)解析字符串用于把输入 token 转换为数字节点。3. 非终结符表达式二元运算更复杂的表达式是PlusExpression、MinusExpression、MultiplyExpression这类二元运算它们各自持有左右两个子表达式。以PlusExpression为例其余两者结构相同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 ; } }MinusExpression的interpret()返回leftExpression.interpret() - rightExpression.interpret()toString()返回-MultiplyExpression则返回两者乘积toString()返回*。解释器的求值过程因此呈现出递归下降的特征非终结符节点的interpret()会先求值其子节点再执行自身运算最终收敛到NumberExpression的整数值。4. 客户端栈式后缀表达式解析有了表达式类层次后App.java 展示了如何将解释器模式用于解析简单的数学表达式。因为采用后缀记法客户端借助一个StackExpression来暂存已解析的表达式Slf4j public class App { 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()); } public static boolean isOperator(String s) { return s.equals() || s.equals(-) || s.equals(*); } 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); }; } }核心执行流程如下用split( )将输入串切分为 token 序列遇到数字 token 时封装成NumberExpression压入栈顶遇到运算符 token 时从栈顶弹出右表达式与左表达式通过getOperatorInstance的switch表达式创建对应的运算节点default分支即乘法调用interpret()求值把运算结果封装成新的NumberExpression重新压栈所有 token 处理完毕后栈顶即最终结果stack.pop().interpret()输出答案。注意getOperatorInstance用到了 Java 的switch 表达式语法因此本模块的编译环境需支持 Java 14 及以上版本如仓库根 pom.xml 所配置的版本。5. 运行结果执行App.main得到的控制台输出如下13:33:15.437 [main] INFO com.iluwatar.interpreter.App -- push to stack: 4 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 3 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 2 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 3 right: 2 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: - 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 1 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push to stack: 1 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 1 right: 1 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 2 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- popped from stack left: 4 right: 2 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- operator: * 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- push result to stack: 8 13:33:15.440 [main] INFO com.iluwatar.interpreter.App -- result: 8对应运算过程为3 - 2 11 1 24 * 2 8等价于中缀表达式4 * (3 - 2 1)。测试验证解释器正确性的自动化保障仓库为每个表达式类都提供了对应的测试位于 interpreter/src/test/java/com/iluwatar/interpreterExpressionTest.java——抽象测试基类通过参数化测试ParameterizedTestMethodSource对解释结果和toString()进行双重校验prepareParameters会生成-10到9之间的全部整数对组合用IntBinaryOperator计算期望结果后逐一比对interpret()返回值同时断言toString()与期望的运算符符号一致。具体的PlusExpressionTest、MinusExpressionTest、MultiplyExpressionTest通过各自的工厂方法与期望符号继承这一测试逻辑。NumberExpressionTest.java 与 AppTest.java——前者验证数字节点的解析与求值后者通过assertDoesNotThrow(() - App.main(new String[] {}))确认整个解析主流程可无异常执行。这一测试结构从侧面印证了解释器模式的可扩展性新增一种运算符如除法只需要新增一个表达式子类并补充对应测试无需改动既有文法与客户端解析逻辑。优缺点与权衡优点可以轻松添加新的解释操作而无需修改文法或既有数据的类结构文法直接以语言类层次的形式实现易于修改和扩展语法树天然是 Composite 模式 的实例节点递归组合、求值递归下降结构清晰。权衡对于大型文法类层次会变得复杂且低效每个文法规则对应一个类复杂语法会导致类数量激增当效率成为关键指标时直接解释语法树通常不是最优方案应考虑先转换为状态机等形式。相关模式Composite解释器模式常与组合模式配合使用用组合模式把文法表示为树形结构叶子为终结符、内部节点为非终结符。Flyweight适用于共享状态、降低内存占用尤其当解释器需要处理语言中的大量重复元素时很有价值。如何查看与运行本示例仓库是只读的你可以按以下方式查阅与运行本模块阅读本模块英文原文文档 interpreter/README.md 及本文对应的 中文翻译文档查看全部源码Expression.java、NumberExpression.java、PlusExpression.java、MinusExpression.java、MultiplyExpression.java、App.java在本模块目录下执行mvn test即可运行全部单元测试含覆盖 -10 至 9 全组合的参数化测试执行mvn exec:java或直接运行App.main即可复现上述控制台输出。参考资料本文核心内容继承自 Design Patterns: Elements of Reusable Object-Oriented SoftwareGoF 经典著作、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示例工程教程解释器模式Interpreter Pattern详解从定义到 C 源码实战——Unity3DTraining 设计模式系列解释器模式Interpreter Pattern详解从定义到 C 源码实战——Unity3DTraining 设计模式系列 导读 本文以 DesignPa示例工程Java 设计模式之装饰器模式Decoratorjava-design-patterns 实战指南Java 设计模式之装饰器模式Decoratorjava design patterns 实战指南 装饰器模式Decorator又称 Wrapper示例工程教程上一篇Python MIDI编程新范式Mido如何重塑音乐技术栈的架构设计下一篇如何高效使用EdB Prepare CarefullyRimWorld终极角色定制指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
阅读完成 · 觉得有帮助?
咨询建站