设计模式-解释器模式(Interpreter)
1. 概念
- 解释器模式(Interpreter Pattern)是一种行为型设计模式,它用于定义一个语言的文法,并解析语言中的表达式。具体来说,解释器模式通过定义一个解释器来解释语言中的表达式,从而实现对语言的解析和执行。
- 在解释器模式中,语言中的每个符号都被定义为一个(对象)类,这样整个程序就被转换成一个具体的对象树。每个节点(即对象)都表示一个表达式中的一个符号,而整棵树则表示一个完整的表达式。通过遍历这棵树,解释器就可以对表达式进行解析和执行。
2. 原理结构图
- 抽象表达式(AbstractExpression)
- 定义一个接口,用于解释一个特定的文法规则。
- 这个接口一般声明了一个解释方法(如 interpret()),用于解释该表达式,并返回一个解释结果。
- 所有的具体表达式都应该实现这个接口。
- 终结符表达式(TerminalExpression)
- 实现了抽象表达式接口,对应于文法中的终结符。
- 终结符是最基本的文法单位,它们不能再进一步分解。
- 在解释器模式中,终结符表达式通常包含对终结符的具体解释逻辑。
- 非终结符表达式(NonterminalExpression)
- 同样实现了抽象表达式接口,对应于文法中的非终结符。
- 非终结符是由其他文法单位(终结符或非终结符)组成的表达式。
- 非终结符表达式通常包含对其他表达式的引用,以及一个解释方法,用于解释其包含的表达式。
- 环境(Context)
- 这是一个可选的角色,用于存储解释过程中需要的信息。
- 在某些复杂的解释场景中,解释器可能需要依赖一些上下文信息来正确地解释表达式。
- 环境对象可以被传递给解释器,以提供这些信息。
- 客户端(Client)
- 客户端负责构建抽象语法树(AST)。
- 客户端将输入的表达式转换为一系列具体的表达式对象(终结符表达式和非终结符表达式),并将它们组合成一个语法树。
- 然后,客户端调用语法树的根节点的解释方法,开始解释过程。
3. 代码示例
3.1 示例1
- 以下是一个基于解释器模式的复杂计算器示例,它支持加、减、乘、除运算以及括号优先级。
- 我们将实现一个简单的表达式求值器,它可以处理类似 “2 + 3 * (4 - 1)” 这样的表达式。
// 定义表达式和项(term)的接口和具体实现
// 抽象表达式接口
interface Expression {double interpret();
}// 抽象项接口
interface Term extends Expression {Expression getLeftOperand();Expression getRightOperand();void setLeftOperand(Expression leftOperand);void setRightOperand(Expression rightOperand);
}// 数字项(直接数值)
class NumberTerm implements Expression {private double value;public NumberTerm(double value) {this.value = value;}@Overridepublic double interpret() {return value;}
}// 二元运算符项(加、减、乘、除)
class BinaryOperatorTerm implements Term {private Expression leftOperand;private Expression rightOperand;private char operator;public BinaryOperatorTerm(char operator) {this.operator = operator;}@Overridepublic Expression getLeftOperand() {return leftOperand;}@Overridepublic void setLeftOperand(Expression leftOperand) {this.leftOperand = leftOperand;}@Overridepublic Expression getRightOperand() {return rightOperand;}@Overridepublic void setRightOperand(Expression rightOperand) {this.rightOperand = rightOperand;}@Overridepublic double interpret() {switch (operator) {case '+':return leftOperand.interpret() + rightOperand.interpret();case '-':return leftOperand.interpret() - rightOperand.interpret();case '*':return leftOperand.interpret() * rightOperand.interpret();case '/':if (rightOperand.interpret() == 0) {throw new ArithmeticException("Division by zero");}return leftOperand.interpret() / rightOperand.interpret();default:throw new UnsupportedOperationException("Unsupported operator: " + operator);}}
}
// 需要一个解析器来将字符串表达式转换为表达式树
// 表达式解析器(这里为了简化,我们假设输入是格式良好的)
class ExpressionParser {// 解析方法(这里仅作为示例,真实情况下需要更复杂的解析逻辑)public Expression parse(String expression) {// 简化解析逻辑,使用堆栈实现,此处略// ...// 示例:直接构造一个表达式树BinaryOperatorTerm add = new BinaryOperatorTerm('+');NumberTerm two = new NumberTerm(2);NumberTerm three = new NumberTerm(3);BinaryOperatorTerm multiply = new BinaryOperatorTerm('*');NumberTerm four = new NumberTerm(4);NumberTerm one = new NumberTerm(1);BinaryOperatorTerm subtract = new BinaryOperatorTerm('-');subtract.setLeftOperand(four);subtract.setRightOperand(one);multiply.setLeftOperand(three);multiply.setRightOperand(subtract);add.setLeftOperand(two);add.setRightOperand(multiply);return add;}
}// 使用解析器来解析表达式并计算结果
public class Calculator {public static void main(String[] args) {ExpressionParser parser = new ExpressionParser();Expression expression = parser.parse("2 + 3 * (4 - 1)"); // 这里我们假设parse方法已经正确实现了double result = expression.interpret();System.out.println("Result: " + result); // 应该输出 "Result: 11.0"}
}
- 将看到如下输出:
Result: 11.0
- 注意:上述代码中的parse方法是简化的,仅用于展示。在真实应用中,你需要编写一个完整的解析器来将字符串表达式转换为表达式树。这通常涉及使用栈数据结构、正则表达式、或者递归下降解析器等高级技术。此外,还需要处理错误情况,如无效的表达式、除零错误等。
3.2 示例2
- 当涉及到领域特定语言(DSL)时,解释器模式可以用来实现一个自定义的、针对特定领域的语法解析和执行系统。以下是一个模拟的复杂场景案例,该场景是一个简单的订单处理DSL,它允许用户以特定的语法输入订单信息,并解释这些信息以进行订单处理。
import java.util.HashMap;
import java.util.Map;// 定义抽象表达式接口和上下文类
// 抽象表达式接口
interface Expression {Object interpret(Context context);
}// 上下文类,用于存储解释器可能需要的数据
class Context {private Map<String, Object> variables = new HashMap<>();public void setVariable(String name, Object value) {variables.put(name, value);}public Object getVariable(String name) {return variables.get(name);}
}// 定义具体的表达式类
// 数值表达式
class NumberExpression implements Expression {private double value;public NumberExpression(double value) {this.value = value;}@Overridepublic Object interpret(Context context) {return value;}
}// 变量表达式
class VariableExpression implements Expression {private String name;public VariableExpression(String name) {this.name = name;}@Overridepublic Object interpret(Context context) {return context.getVariable(name);}
}// 加法表达式
class AddExpression implements Expression {private Expression left;private Expression right;public AddExpression(Expression left, Expression right) {this.left = left;this.right = right;}@Overridepublic Object interpret(Context context) {double leftValue = (double) left.interpret(context);double rightValue = (double) right.interpret(context);return leftValue + rightValue;}
}// 其他运算表达式(如减法、乘法、除法)可以类似地定义
// ...// 订单项表达式(包含数量和单价)
class OrderItemExpression implements Expression {private Expression quantity;private Expression price;public OrderItemExpression(Expression quantity, Expression price) {this.quantity = quantity;this.price = price;}@Overridepublic Object interpret(Context context) {double qty = (double) quantity.interpret(context);double priceValue = (double) price.interpret(context);return qty * priceValue; // 计算订单项总价}
}// 创建一个解析器类来解析DSL字符串,并构建表达式树
class DSLParser {// 假设这里有一个复杂的解析算法,可以将DSL字符串转换为Expression对象树// ...// 示例方法,用于演示如何构建表达式树public Expression parseOrderDSL(String dsl) {// 假设dsl是 "item(2, 10) + item(3, 5)",表示两个订单项,第一个数量为2,单价为10;第二个数量为3,单价为5// 这里为了简单起见,直接构建表达式树return new AddExpression(new OrderItemExpression(new NumberExpression(2), new NumberExpression(10)),new OrderItemExpression(new NumberExpression(3), new NumberExpression(5)));}
}public class OrderProcessor {public static void main(String[] args) {DSLParser parser = new DSLParser();Expression orderExpr = parser.parseOrderDSL("item(2, 10) + item(3, 5)");Context context = new Context();// 在这里可以设置一些全局变量或环境信息,如果有需要的话double total = (double) orderExpr.interpret(context);System.out.println("Order total: " + total); // 输出 "Order total: 35.0"}
}
- 将看到如下输出:
Order total: 35.0
- 注意:上面的代码是一个简化的示例,真实的DSL解析器可能会涉及到词法分析、语法分析、错误处理等更复杂的步骤。此外,为了处理更复杂的DSL,你可能需要定义一个更复杂的表达式树结构,以及更多的具体表达式类来处理不同的DSL元素。
4. 优缺点
- 主要作用
- 定义一个语言的文法,并构建一个解释器来解释该语言中的句子。
- 优点
- 灵活性:解释器模式允许用户定义新的解释表达式的方式,从而方便地扩展和改变语言的解析和执行规则。
- 可扩展性:通过定义新的解释器类,可以很容易地增加新的文法规则,而无需修改现有的代码,符合开闭原则。
- 复用性:由于解释器类通常针对特定的文法规则进行实现,因此这些类可以在多个解析场景中被复用,提高了代码的复用性。
- 易于实现:解释器模式通过面向对象的方式将文法规则映射为解释器类,使得复杂的文法规则更容易被理解和实现。
- 清晰的结构:解释器模式将文法规则与解释逻辑分离,使得代码结构更加清晰,易于维护和扩展。
- 缺点
- 执行效率低:由于解释器模式中通常使用大量的循环和递归调用,当要解释的句子较复杂时,其运行速度会显著下降,且代码的调试过程也比较麻烦。
- 会引起类膨胀:因为每条文法规则至少需要定义一个类,所以当包含的文法规则很多时,类的个数将急剧增加,导致系统难以管理与维护。
- 可应用的场景比较少:在软件开发中,需要定义语言文法的应用实例非常少,这种模式很少被使用到,限制了其实用性。
5. 应用场景
5.1 主要包括以下几个方面
- 领域特定语言(DSL):当需要定义和处理特定领域的语言时,例如配置文件解析、查询语言、规则引擎等,解释器模式可以帮助你实现对这些语言的解释和执行。
- 编译器和解析器:解释器模式在编译器、解析器等领域中得到广泛应用,用于将源代码解析成中间代码或目标代码,或者将文本解析成抽象语法树进行进一步处理。
- 正则表达式引擎:正则表达式是一种特定的领域语言,解释器模式可以用来解释和执行正则表达式,实现字符串匹配和替换等功能。
- 规则引擎:当需要实现复杂的规则和条件判断时,解释器模式可以帮助你定义规则并进行解释执行,例如业务规则引擎、决策引擎等。
- 数学表达式解析:解释器模式可以用于解析和计算数学表达式,实现类似计算器的功能。
- 自然语言处理:在一些自然语言处理场景中,解释器模式可以用来处理文本的语法和语义,进行语法分析、语义分析等操作。
- 自定义语言:解释器模式可以用于创建自定义的领域特定语言(DSL),以便更好地描述和解决特定领域的问题。例如,SQL是一种用于查询数据库的领域特定语言,它可以使用解释器模式来解析和执行查询语句。
6. JDK中的使用
在Java JDK中,解释器模式的一个典型应用是Java的正则表达式库,特别是java.util.regex包下的相关类。这些类提供了一个强大的工具集,用于解析、匹配和操作文本字符串,它们正是基于解释器模式设计的。
- 具体使用
- 在Java中,使用正则表达式主要涉及以下几个类:
- Pattern:这个类表示一个编译过的正则表达式。它提供了很多方法,如compile(String regex)用于编译一个正则表达式字符串为一个Pattern对象,matcher(CharSequence input)用于创建一个Matcher对象以匹配输入字符串。
- Matcher:这个类用于执行匹配操作。它提供了很多方法,如find()用于查找下一个匹配项,group()用于获取匹配的子序列,matches()用于尝试将整个区域与模式匹配等。
- 分析
- 抽象表达式(AbstractExpression):
- 在正则表达式库中,抽象表达式可以看作是Pattern类。Pattern类定义了一个正则表达式的编译表示,并提供了一个接口(即方法)来与输入文本进行匹配。
- 终结符表达式(TerminalExpression):
- 正则表达式中的每个原子元素(如字符、数字、特殊字符等)都可以看作是终结符表达式。然而,在Java的正则表达式库中,这些终结符的具体实现是隐藏在内部的,用户不需要直接处理它们。
- 非终结符表达式(NonterminalExpression):
- 非终结符表达式对应于正则表达式中的复合结构,如序列(abc)、选择(a|b)、重复(a*)等。这些结构由多个终结符或非终结符组成。在Java中,这些结构是通过正则表达式的元字符和量词来表示的,而具体的解析和匹配逻辑则由Pattern类内部处理。
- 环境(Context):
- 在Java的正则表达式库中,环境通常包括输入文本、匹配位置、匹配结果等信息。这些信息被封装在Matcher类中,并通过其方法(如find(), group()等)与用户交互。
- 客户端(Client):
- 客户端是调用正则表达式库代码的应用程序。它负责编译正则表达式、创建Matcher对象,并调用Matcher对象的方法来执行匹配操作。
- 抽象表达式(AbstractExpression):
7. 注意事项
- 类膨胀:当文法规则复杂时,可能会导致解释器类的大量增加,造成类膨胀,增加系统的复杂性。
- 性能问题:由于解释器模式通常涉及递归调用,对于复杂的表达式,可能会导致性能下降。
- 调试困难:由于解释器模式可能涉及多层嵌套的解释器对象,调试时可能会变得非常复杂。
- 适用场景:解释器模式适用于需要解释执行特定语法或表达式的场景,如编译器、表达式求值等。对于简单的规则或逻辑,可能不需要使用解释器模式。
- 避免过度设计:不要过度使用解释器模式,特别是在简单的逻辑或不需要灵活扩展的场景下。过度设计会增加系统的复杂性和维护成本。
- 考虑其他替代方案:在决定使用解释器模式之前,应考虑是否有其他更简单的替代方案,如使用现有的解析库或脚本语言等。
8. 解释器模式 VS 状态模式
模式 | 类型 | 目的 | 模式架构核心角色 | 应用场景 |
---|---|---|---|---|
迭代器模式 | 行为型 | 用于解析和处理特定语法或表达式的语言 | 抽象表达式(Expression)、具体表达式(ConcreteExpression)、环境(Context) | 当需要一种语言,该语言的解释程序可以用程序来扩展或修改时 |
状态模式 | 行为型 | 消除因多种状态导致的复杂条件判断语句,使程序更加清晰和易于维护 | 上下文(Context)、抽象状态(State)、具体状态(ConcreteState) | 当一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为时 |
相关文章:

设计模式-解释器模式(Interpreter)
1. 概念 解释器模式(Interpreter Pattern)是一种行为型设计模式,它用于定义一个语言的文法,并解析语言中的表达式。具体来说,解释器模式通过定义一个解释器来解释语言中的表达式,从而实现对语言的解析和执…...

机器视觉任务中语义分割方法的进化历史
机器视觉任务中语义分割方法的进化历史 一、基于传统方法的图像分割二、基于卷积神经网络的图像分割三、基于Attention机制的图像分割四、语义分割模型的挑战与改进 在图像处理领域,传统图像分割技术扮演着重要角色。 一、基于传统方法的图像分割 这些方法包括大津…...

Java并发编程: Synchronized锁升级
文章目录 一、jdk8 markword实现表二、使用工具来查看锁升级三、默认synchronized(o) 一、jdk8 markword实现表 为什么有自旋锁还需要重量级锁: 自旋消耗CPU资源,如果锁的时间长,或者自旋线程多,CPU会被大量消耗。重量…...

Atcoder C - Routing
https://atcoder.jp/contests/arc177/tasks/arc177_c 思路:该问题可以归约为最短路问题,问题中的条件1和条件2是相互独立的,可以分开考虑,从地图中的一个点,沿上下左右四个方向走,所花费的代价为࿱…...

升级! 测试萌新Python学习之连通数据库Pymsql增删改及封装(四)
pymysql 数据库概述python对数据库的增删改查pymysql核心操作事务事务操作pymysql工具类封装每日复习ChatGPT的回答 数据库概述 分类 关系型数据库: 安全 如, mysql oracle SQLite…database tables 行列 非关系型数据库: 高效 如, redis mongoDB…数据存储结构多样 键值对…...

【大数据】containered学习笔记
文章目录 1. Containerd安装1.1 YUM方式安装 【后端&网络&大数据&数据库目录贴】 1. Containerd安装 1.1 YUM方式安装 获取YUM源 获取阿里云YUM源 wget -O /etc/yum.repos.d/docker-ce.repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo 查…...

「TypeScript」TypeScript入门练手题
前言 TypeScript 越来越火,现在很多前端团队都使用它,因此咱们前端码农要想胜任以后的前端工作,就要更加熟悉它。 入门练手题 interface A {x: number;y: number; }type T Partial<A>;const a: T { x: 0, y: 0 }; const b: T { …...

k8s 使用Docker和Containerd对比分析
目录 k8s 使用Docker和Containerd对比分析 互动1:docker build构建的镜像和containerd镜像通用吗? 互动2:k8s1.24之前版本和1.24及1.24之后版本区别? k8s 使用Docker和Containerd对比分析 如果你使用Docker作为K8S容器运行时的…...

MySQL 通过 systemd 启动时 hang 住了……
mysqld:哥,我起不来了…… 作者:贲绍华,爱可生研发中心工程师,负责项目的需求与维护工作。其他身份:柯基铲屎官。 爱可生开源社区出品,原创内容未经授权不得随意使用,转载请联系小编…...

pat乙1033-旧键盘打字
1测试点2: 输入的字符串如果为空,要用getline(cin,s),而不是cin>>s,否则程序做不了 2题目说的如果上键坏了那大写字母打印不了,不是大写转小写打印啦,认真读题 3两个for循环长这样,break…...

Ubuntu安装VScode
Ubuntu安装VScode 前言: 1、Ubuntu安装VScode比较方便 2、我更喜欢source insight 1、获取到linux版本的VScode安装包 VSCode 下载地址是:https://code.visualstudio.com/ 2、得到安装包 3、复制到ubuntu中,使用命令安装 sudo dpkg -i cod…...

c# - - - winform程序四个角添加圆角效果
winform 给窗体四个角添加圆角效果。 在窗体 Load 事件中添加如下代码: // 创建了一个圆角矩形的路径,并将其设置为控件的形状 System.Drawing.Drawing2D.GraphicsPath path new System.Drawing.Drawing2D.GraphicsPath(); int radius 30; path.AddAr…...

Springboot 集成 Consul 实现服务注册中心-05
因为后续很多模块都要用到注册中心,所以此处先实现此模块。 Consul简介 Consul是一个开源的服务发现和配置管理工具,具有跨平台、运行高效等特点。它由HashiCorp公司开发,并使用Go语言编写。Consul主要用于实现分布式系统中的服务发现、健康…...

【软考高项】四十六、项目管理科学计算之运筹学
1、线性规划问题 解题思路: 先把文字转化成图表 最快方式应该是把第一题的4个答案直接代入计算,很快得知X2时利润最大。 A0时,利润5*630 A2时,利润2*25*634 A4时,利润4*23*523 A6时,利润4*2(因为甲的…...

使用 Python 和 OpenCV 进行实时目标检测的详解
使用到的模型文件我已经上传了,但是不知道能否通过审核,无法通过审核的话,就只能 靠大家自己发挥实力了,^_^ 目录 简介 代码介绍 代码拆解讲解 1.首先,让我们导入需要用到的库: 2.然后,设…...

Android build.prop生成过程源码分析
Android的build.prop文件是在Android编译时刻收集的各种property【LCD density/语言/编译时间, etc.】;编译完成之后,文件生成在out/target/product/<board【OK1000】>/system/目录下;在Android运行时刻可以通过property_get()[c/c域] …...

计算机网络教材——谢希仁教材与配套PPT课件和《计算机网络——自顶向下方法》
教材链接: https://pan.baidu.com/s/1MUkgTVNMvhFdkGxAd0U7Ew?pwdn3g4 提取码: n3g4 ppt资源:课程包列表 (51zhy.cn) 计算机网络——自顶向下方法(资源在下面的评论区里):计算机网络自顶向下方法第7版中文PDF习题参考 - 哔哩哔…...

mysql 离线安装
package download mysql https://dev.mysql.com/downloads/mysql/ libaio http://mirror.centos.org/centos/7/os/x86_64/Packages/libaio-0.3.109-13.el7.x86_64.rpm 根据自己服务器选择下载对应的安装包及依赖 删除本机自带mysql相关 # 首先排查服务器自身是否有安装对应m…...

【C++】 string类:应用与实践
💞💞 前言 hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹 💥个人主页&#x…...

巩固学习7
正则表达式 就是用来找到符合模式的字符串,这些模式包括:是什么字符,重复多少次,在什么位置,有哪些额外的约束 找某个字符串 import re text身高:178 体重:168 学号:123456 密码:9527 #在Python中,r前缀用…...

Android 右键 new AIDL 无法选择
提示 (AIDL File)Requires setting the buildFeatures.aidl to true in the build file) 解决方式: 在app的build.gradl中 adnroid{} 添加: buildFeatures{aidl true}...

使用Springboot整合Elasticsearch
全文搜索引擎 全文搜索引擎是目前广泛应用的主流搜索引擎,也称为全文检索。它的工作原理是计算机索引程序通过扫描文章中的每一个词,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序…...

Vue3+Element+TS动态菜单+按钮权限控制实现探索
1.动态获取权限并根据获取权限转换成相对应的router 根据请求获取菜单数据,对菜单数据进行转换,分别进行下面几步: /*** 组件地址前加斜杠处理*/ export function addSlashToRouteComponent(routeList: AppRouteRecordRaw[]) {routeList.fo…...

五款公司源代码加密软件推荐|代码防泄密解决方案
在当今数字化的世界中,源代码的泄露无疑是一场灾难。对于依赖加密软件保护关键信息的企业和个人来说,这种泄露不仅可能导致数据失窃,还可能损害企业的声誉和客户的信任。面对这种严峻的形势,我们迫切需要一种全面而有效的加密软件…...

【spring】Security 密码加密算法
Spring Security 提供了多种密码加密算法,用于在存储用户密码时进行加密,以增强安全性。 查看org.springframework.security.crypto.factory.PasswordEncoderFactories 以下是一些常用的密码加密算法: BCryptPasswordEncoder: 这…...

IO系列(一) -一文带你读懂 java 中的IO流!
一、摘要 说到 IO,相信大家都不陌生,英文全称:Input/Output,即输入/输出,通常指数据在内部存储器和外部存储器或其他周边设备之间的输入和输出。 比如我们常用的SD卡、U盘、移动硬盘等等存储文件的硬件设备ÿ…...

代码随想录算法训练营第六天| 242. 有效的字母异位词、349. 两个数组的交集、202. 快乐数、1. 两数之和
哈希表理论基础 [LeetCode] 242. 有效的字母异位词 [LeetCode] 242. 有效的字母异位词 文章解释 [LeetCode] 242. 有效的字母异位词 视频解释 题目: 给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。 注意:若 s 和 t 中每个字符出…...

【python】中的可迭代对象、迭代器、生成器
结论 凡是实现了__iter__() 方法的类都称之为可迭代对象,但 __iter__() 方法的返回值只能是迭代器和生成器for 循环的本质是先调用 __iter__() 方法,然后不断调用返回值的 __next__() 方法,直至报出异常 StopIteration,可迭代对象…...

短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发
短视频矩阵系统源码/saas--总后台端、商户端、代理端、源头开发 搭建短视频矩阵系统源码的交付步骤可以概括为以下几个关键环节: 1. **系统需求分析**:明确系统需要支持的功能,如短视频的上传、存储、播放、分享、评论、点赞等。 2. **技术选…...

K8s:二进制安装k8s(单台master)
目录 一、安装k8s 1、拓扑图 2、系统初始化配置 2.1关闭防火墙selinx以及swap 2.2设置主机名 2.3在每台主机中添加hosts,做映射 2.4调整内核参数,将桥接的ipv4流量传递到iptables,关闭ipv6 2.4时间同步 3、部署docker引擎࿰…...