行为型设计模式:解释器模式

解释器模式

解释器模式介绍

解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。

解释器模式(Interpreter pattern)原始定义:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。

我们通过一个例子给大家解释一下解释器模式。

  • 假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
//用于两个整数相加的方法
public static int add(int a , int  b){return a + b;
}//用于三个整数相加的方法
public static int add(int a , int  b,int c){return a + b + c;
}public static int add(Integer ... arr){int sum = 0;for(Integer num : arr){sum += num;}return sum;
}+ - 

上面的形式比较单一、有限,然而现实生活中情况就比较复杂,形式变化非常多,这就不符合要求,因为加法和减法运算,两个运算符与数值可以有无限种组合方式。比如: 5-3+2-1, 10-5+20…

文法规则和抽象语法树

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。

在上面提到的加法/减法解释器中,每一个输入表达式(比如:2+3+4-5) 都包含了3个语言单位,可以使用下面的文法规则定义:

文法是用于描述语言的语法结构的形式规则。

expression ::= value | plus | minus 
plus ::= expression ‘+’ expression   
minus ::= expression ‘-’ expression  
value ::= integer

注意: 这里的符号“::=”表示“定义为”的意思,竖线 | 表示或,左右的其中一个,引号内为字符本身,引号外为语法。

上面规则描述为 :表达式可以是一个值,也可以是 plus 或者 minus 运算,而 plus 和 minus 又是由表达式结合运算符构成,值的类型为整型数。

抽象语法树:

在解释器模式中还可以通过一种称为抽象语法树的图形方式来直观的表示语言的构成,每一棵抽象语法树对应一个语言实例,例如加法/减法表达式语言中的语句 " 1+ 2 + 3 - 4 + 1" 可以通过下面的抽象语法树表示
在这里插入图片描述

解释器模式原理

接下来我们画个UML类图,通过该类图来了解下该模式的原理。

在这里插入图片描述

从上述UML类图中,看到解释器模式包含以下几种主要角色。

  • 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
  • 终结符表达式(Terminal Expression)角色:抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式.
  • 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式
  • 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
  • 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。

解释器模式实现

为了更好的给大家解释一下解释器模式,我们来定义了一个进行加减乘除计算的“语言”,语法规则如下:

  • 运算符只包含加、减、乘、除,并且没有优先级的概念;
  • 表达式中,先书写数字,后书写运算符,空格隔开;

我们举个例子来解释一下上面的语法规则:

  • 比如“ 9 5 7 3 - + * ”这样一个表达式,我们按照上面的语法规则来处理,取出数字 “9、5”“-” 运算符,计算得到 4,于是表达式就变成了“ 4 7 3 + * ”。然后,我们再取出“4 7”和“ + ”运算符,计算得到 11,表达式就变成了“ 11 3 * ”。最后,我们取出“ 11 3”和“ * ”运算符,最终得到的结果就是 33。

代码示例:

  • 用户按照上面的规则书写表达式,传递给 interpret() 函数,就可以得到最终的计算结果。
/*** 表达式解释器类**/
public class ExpressionInterpreter {//Deque双向队列,可以从队列的两端增加或者删除元素private Deque<Long>  numbers = new LinkedList<>();//接收表达式进行解析public long interpret(String expression){String[] elements = expression.split(" ");int length = elements.length;//获取表达式中的数字for (int i = 0; i < (length+1)/2; ++i) {//在 Deque的尾部添加元素numbers.addLast(Long.parseLong(elements[i]));}//获取表达式中的符号for (int i = (length+1)/2; i < length; ++i) {String operator = elements[i];//符号必须是 + - * / 否则抛出异常boolean isValid = "+".equals(operator) || "-".equals(operator)|| "*".equals(operator) || "/".equals(operator);if (!isValid) {throw new RuntimeException("Expression is invalid: " + expression);}//pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值long number1 = numbers.pollFirst(); //数字long number2 = numbers.pollFirst();long result = 0;  //运算结果//对number1和number2进行运算if (operator.equals("+")) {result = number1 + number2;} else if (operator.equals("-")) {result = number1 - number2;} else if (operator.equals("*")) {result = number1 * number2;} else if (operator.equals("/")) {result = number1 / number2;}//将运算结果添加到集合头部numbers.addFirst(result);}//运算完成numbers中应该保存着运算结果,否则是无效表达式if (numbers.size() != 1) {throw new RuntimeException("Expression is invalid: " + expression);}//移除Deque的第一个元素,并返回return numbers.pop();}
}

代码重构

上面代码的所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这时候,我们就要考虑拆分代码进行优化了,将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。 利用解释器模式,我们把解析的工作拆分到以下五个类:num、plu、sub、mul和div

  • NumExpression
  • PluExpression
  • SubExpression
  • MulExpression
  • DivExpression
/*** 表达式接口**/
public interface Expression {long interpret();
}/*** 数字表达式**/
public class NumExpression implements Expression {private long number;public NumExpression(long number) {this.number = number;}public NumExpression(String number) {this.number = Long.parseLong(number);}@Overridepublic long interpret() {return this.number;}
}/*** 加法运算**/
public class PluExpression implements Expression{private Expression exp1;private Expression exp2;public PluExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() + exp2.interpret();}
}/*** 减法运算**/
public class SubExpression implements Expression {private Expression exp1;private Expression exp2;public SubExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() - exp2.interpret();}
}/*** 乘法运算**/
public class MulExpression implements Expression {private Expression exp1;private Expression exp2;public MulExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() * exp2.interpret();}
}/*** 除法**/
public class DivExpression implements Expression {private Expression exp1;private Expression exp2;public DivExpression(Expression exp1, Expression exp2) {this.exp1 = exp1;this.exp2 = exp2;}@Overridepublic long interpret() {return exp1.interpret() / exp2.interpret();}
}//测试
public class Test01 {public static void main(String[] args) {ExpressionInterpreter e = new ExpressionInterpreter();long result = e.interpret("6 2 3 2 4 / - + *");System.out.println(result);}
}

上面的代码看起来是不是清晰多了,有空的家人也去动手试试吧!

解释器模式总结

1) 解释器优点

  • 易于改变和扩展文法

    因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法。每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言

  • 实现文法比较容易

    在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂

  • 增加新的解释表达式比较方便

    如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了。原有的表达式类不需要修改,符合开闭原则

2) 解释器缺点

  • 对于复杂文法难以维护

    在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理.

  • 执行效率低

    在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐

3) 使用场景

  • 当语言的文法比较简单,并且执行效率不是关键问题.
  • 当问题重复出现,且可以用一种简单的语言来进行表达
  • 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/92010.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/92010.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SaTokenException: 未能获取对应StpLogic 问题解决

&#x1f4dd; Sa-Token 异常处&#xff1a;未能获取对应StpLogic&#xff0c;typeuser&#x1f9e8; 异常信息 cn.dev33.satoken.exception.SaTokenException: 未能获取对应StpLogic&#xff0c;typeuser抛出位置&#xff1a; throw new SaTokenException("未能获取对应S…

Web前端性能优化原理与方法

一、概述 1.1 性能对业务的影响 大部分网站的作用是&#xff1a;产品信息载体、用户交互工具或商品流通渠道。这就要求网站与更多用户建立联系&#xff0c;同时还要保持良好的用户黏性&#xff0c;所以网站就不能只关注自我表达&#xff0c;而不顾及用户是否喜欢。看看网站性…

第十八节:第六部分:java高级:注解、自定义注解、元注解

认识注解自定义注解注解的原理元注解常用的两个元注解代码&#xff1a; MyTest1&#xff08;注解类&#xff09; package com.itheima.day10_annotation;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.Retent…

北京科技企业在软文推广发稿平台发布文章,如何精准触达客户?

大家好&#xff01;我是你们的老朋友&#xff0c;今天咱们聊聊北京科技企业如何通过软文推广发稿平台精准触达目标客户这个话题。作为企业营销的老司机&#xff0c;我深知在这个信息爆炸的时代&#xff0c;如何让你的品牌声音被目标客户听到是多么重要。下面就让我来分享一些实…

UE蒙太奇和动画序列有什么区别?

在 UE5 中&#xff0c;Animation Sequence&#xff08;动画序列&#xff09;和 Animation Montage&#xff08;动画蒙太奇&#xff09;虽然都能播放骨骼动画&#xff0c;但它们的定位、功能和使用场景有较大区别&#xff1a;1. 概念定位Animation Sequence&#xff08;动画序列…

Nordic打印RTT[屏蔽打印中的<info> app]

屏蔽打印中的 app Nordic原装的程序答应是这样的,这个有" app"打印,因为习惯问题,有时候也不想打印太多造成RTT VIEW显示被冲点,所以要把" app"去掉:这里把prefix_process函数调用屏蔽到,主要涉及到nrf_log_hexdump_entry_process和nrf_log_std_entry_proc…

Python基础和高级【抽取复习】

1.Python 的深拷贝和浅拷贝有什么区别&#xff1f; 浅拷贝【ls.copy()】&#xff1a; 将列表的不可变对象【值】复制一份&#xff0c;同时引用其中的可变对象【列表】&#xff0c;共用一个内存地址 深拷贝【lscopy.deepcopy(list)】&#xff1a; 完全的复制原可变对象&#xff…

TinyPiXOS组件开发(一):开发规范、组件开发方法介绍,快速上手组件开发,创造各种有趣的UI组件!

本文将通过实现一个点击切换进度的电量指示灯组件和exampleGUI组件库介绍如何基于TinyPiXOS开发新组件。主要内容包括组件开发规范、自定义组件开发和组件库开发三部分。 组件开发规范 命名规范 采用tp开头命名组件类&#xff0c;名称具备易读性。 目录规范 头文件放置 in…

主流熔断方案选型指南

主流熔断方案选型1. Netflix Hystrix (经典但已停止维护)适用场景&#xff1a;传统Spring Cloud项目&#xff0c;需要快速集成熔断功能优点&#xff1a;成熟稳定&#xff0c;社区资源丰富与Spring Cloud Netflix套件无缝集成提供熔断、降级、隔离等完整功能缺点&#xff1a;已停…

Django中get()与filter()对比

在 Django 中&#xff0c;get() 和 filter() 是 QuerySet API 中用于检索数据的两个核心方法&#xff0c;它们的功能和使用场景有明显区别。以下是详细对比&#xff1a; 1. 核心区别特性get()filter()返回值单个对象&#xff08;模型实例&#xff09;查询集&#xff08;QuerySe…

MySQL锁(一) 概述与分类

1.1 MySQL锁的由来 客户端发往 MySQL 的一条条 SQL 语句&#xff0c;实际上都可以理解成一个个单独的事务&#xff08;一条sql语句默认就是一个事务&#xff09;。而事务是基于数据库连接的&#xff0c;每个数据库连接在 MySQL 中&#xff0c;又会用一条工作线程来维护&#x…

PyTorch里的张量及张量的操作

张量的简介 张量是多重线性映射在给定基下的坐标表示&#xff0c;可视为向量和矩阵的泛化。 0 维张量&#xff1a;标量&#xff08;如 5&#xff09;1 维张量&#xff1a;向量&#xff08;如 [1, 2, 3]&#xff09;2 维张量&#xff1a;矩阵&#xff08;如 [[1, 2], [3, 4]]&…

向量数据库Faiss vs Qdrant全面对比

Faiss vs Qdrant 全面对比表 向量数据库是一种相对较新的方式,用于与来自不透明机器学习模型(如深度学习架构)派生的抽象数据表示进行交互。这些表示通常被称为向量或嵌入(embeddings),它们是用于训练机器学习模型完成诸如情感分析、语音识别、目标检测等任务的数据的压…

2025年AIR SCI1区TOP,缩减因子分数阶蜣螂优化算法FORDBO,深度解析+性能实测

目录1.摘要2.蜣螂优化算法DBO原理3.改进策略4.结果展示5.参考文献6.代码获取7.算法辅导应用定制读者交流1.摘要 传统DBO存在探索与开发能力失衡、求解精度低以及易陷入局部最优等问题。因此&#xff0c;本文提出了带有缩减因子分数阶蜣螂优化算法&#xff08;FORDBO&#xff0…

爬虫逆向之JS混淆案例(全国招标公告公示搜索引擎 type__1017逆向)

案例https://ctbpsp.com/#/ 截至2025.07.19可用 定位加密位置 加密位置&#xff1a; 定位方式&#xff0c;XHR&#xff0c;跟栈 跟栈 QL打断点&#xff0c;重新断住 分析为&#xff0c;一个函数传入四个参数 var QL QI[d9(Nv.mQ)](QJ, Qh, Qv, this[d9(Nv.m9)][0xa1a * …

Hive常用命令总结

一、数据库操作 -- 创建数据库&#xff08;默认路径&#xff09; CREATE DATABASE IF NOT EXISTS myhive;-- 指定路径创建数据库 CREATE DATABASE myhive2 LOCATION /myhive2;-- 查看数据库信息 DESC DATABASE myhive;-- 删除数据库&#xff08;强制删除表&#xff09; DROP DA…

Spring整合MyBatis详解

Spring整合MyBatis详解一、整合优势与核心思路1.1 整合优势1.2 核心整合思路二、环境搭建与依赖配置2.1 开发环境2.2 Maven依赖配置三、整合配置&#xff08;核心步骤&#xff09;3.1 数据库配置文件&#xff08;db.properties&#xff09;3.2 Spring配置文件&#xff08;sprin…

Windows CMD(命令提示符)中最常用的命令汇总和实战示例

CMD命令汇总 下面是 Windows CMD&#xff08;命令提示符&#xff09;中最常用的命令汇总&#xff0c;共 30 个&#xff0c;包含说明和典型代码示例&#xff0c;适合日常开发、系统操作、文件管理、网络诊断等场景。一、文件与目录操作&#xff08;最常用&#xff09;命令说明示…

嵌入式硬件篇---舵机(示波器)

舵机是一种高精度的角度控制执行器件&#xff0c;广泛应用于机器人、航模、自动化设备等领域。其核心特点是能通过控制信号精准定位到特定角度&#xff08;通常范围为 0-180&#xff0c;部分可到 360 连续旋转&#xff09;。常见的舵机类型可根据结构、控制方式、用途等维度划分…

嵌入式硬件篇---按键

按键是电子系统中最基础的人机交互部件&#xff0c;通过机械或电子方式实现电路通断或状态切换。根据结构和工作原理的不同&#xff0c;常见按键可分为机械按键、薄膜按键、触摸按键等&#xff0c;以下详细介绍其工作原理、应用场景及电路特点&#xff1a;一、机械按键&#xf…