设计模式之解释器模式的魅力:让代码读懂你的语言

news2024/11/26 12:41:16

目录

一、什么是解释器模式

二、解释器模式的应用场景

三、解释器模式的优缺点

3.1. 优点

3.2. 缺点

四、解释器模式示例

4.1. 问题描述

4.2. 问题分析

4.3. 代码实现

4.4. 优化方向

五、总结


一、什么是解释器模式

    解释器模式(Interpreter pattern)是一种行为型(Behavioral Pattern)的设计模式,用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。该模式将句子表示为一个抽象语法树,每个节点代表一个语法规则,通过递归地解释这些节点来实现对句子的解释。

    解释器模式主要包含以下五类角色:

  • 抽象表达式(Abstract Expression):定义了解释器的接口,包括一个解释方法,并根据文法规则解释表达式。
  • 终结符表达式(Terminal Expression):实现抽象表达式接口,代表文法中的终结符。
  • 非终结符表达式(Non-Terminal Expression):实现抽象表达式接口,代表文法中的非终结符,通常包含其他表达式。
  • 上下文(Context):包含需要被解释的信息或状态,解释器通过上下文来执行解释操作。
  • 客户端(Client):创建和配置具体的解释器表达式,构建解释器的抽象语法树,并调用解释器的解释方法来解释表达式。

    解释器模式的结构如下所示:

二、解释器模式的应用场景

    解释器模式适用于需要解释和执行特定领域语言的场景,常见的适合应用解释器模式的场景包括但不限于:

  • 编程语言解释器:解释器模式最经典的应用就是编程语言的解释器。例如,Python、JavaScript等编程语言都使用解释器来解释和执行代码。
  • 数学表达式解析:解释器模式可以用于解析和计算数学表达式。例如,我们可以使用解释器模式来解析并计算一个复杂的数学公式。
  • 查询语言解析:解释器模式可以用于解析和执行查询语言。例如,数据库查询语言的解析和执行就可以使用解释器模式来实现。

三、解释器模式的优缺点

3.1. 优点

    1)可扩展性:增加新的解释表达式比较方便,扩展时不需要修改原有的逻辑,符合开闭原则。

    2)灵活性:改变或扩展文法都比较容易。

    3)实现文法容易:语法树中的每个表达式节点类都是相似的,易于实现。

3.2. 缺点

    1)类膨胀:每个语法都要产生一个非终结符表达式,可能导致大量类文件。

    2)性能问题:递归解释语法可能导致效率低下。

四、解释器模式示例

4.1. 问题描述

    为了更好地理解解释器模式的应用,我们将以一个简单的例子来演示它的实现过程。假设我们需要设计一个简单的计算器,能够解析并计算用户输入的包含加减乘除四种计算的数学表达式,其中只存在数字和加(+)、减(-)、乘(*)、除(/)四种符号,每种元素之间以空格区分,并且四种运算符号的运算顺序总是从左到右的(即不限定一定要先算乘除后算加减)。

4.2. 问题分析

    我们以一个简单的表达式“2 + 3 * 4 - 5 / 3”为例,其语法树可以表示为:

    其中包含一种终结符表达式(数字表达式)以及四种非终结符表达式(加减乘除四种表达式)。则我们可以:

    1)定义一个抽象表达式接口(AbstractExpression),并包含一个解释方法interpret()用于返回解释结果。

    2)定义一个数字表达式类(NumberExpression),它的内部维护了一个数字对象,并且其实现的interpret()方法直接返回这个数字对象。

    3)为加减乘除四种运算规则分别定义相应的表达式类(AddExpression,SubExpression,MulExpression,DivExpression),它们的内部都维护了AbstractExpression类型的运算的左值(left)和右值(right),并分别按照各自的规则实现interpret()方法返回运算结果。

    4)由于此计算器只需要从左往右顺序地计算下来就可以了,所以表达式中并不需要知道上下文环境,那么我们可以省略定义上下文类(Context),而直接定义客户端类(Client)用于对外提供运算接口(execute(String exp)),将接收到的表达式字符串参数转换成使用解释器对象描述的语法树,并解释并输出解释结果。

4.3. 代码实现

    经过上一步的分析之后,下面我们通过代码来实现它:

interface AbstractExpression{
    public int interpret();
}
​
class NumberExpression implements AbstractExpression{
    private int number;
​
    NumberExpression(int number) {
        this.number = number;
    }
​
    @Override
    public int interpret() {
        return number;
    }
}
//加法
class AddExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    AddExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() + right.interpret();
    }
}
//减法
class SubExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    SubExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() - right.interpret();
    }
}
//乘法
class MulExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    MulExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() * right.interpret();
    }
}
//除法
class DivExpression implements AbstractExpression{
    private AbstractExpression left;
    private AbstractExpression right;
​
    DivExpression(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret() {
        return left.interpret() / right.interpret();
    }
}
​
class Cient{
    public int execute(String exp){
        String[] elements = exp.split(" ");
        if(elements.length % 2 == 0){
            throw new RuntimeException("表达式错误");
        }
        AbstractExpression expression = new NumberExpression(Integer.parseInt(elements[0]));
        for (int i = 1; i < elements.length-1; i+=2) {
            String symbol =  elements[i];
            int num;
            try{
                num = Integer.parseInt(elements[i+1]);
            }catch (NumberFormatException e){
                throw new RuntimeException("表达式错误");
            }
            if (symbol.equals("+")){
                expression = new AddExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("-")){
                expression = new SubExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("*")){
                expression = new MulExpression(expression, new NumberExpression(num));
            }else if (symbol.equals("/")){
                expression = new DivExpression(expression, new NumberExpression(num));
            }else {
                System.out.println("无效的符号");
            }
        }
        return expression.interpret();
    }
}

    接下来编写测试类:

    public static void main(String[] args) {
        Cient cient = new Cient();
        System.out.println(cient.execute("2 + 3 * 4 - 5 / 3"));
        System.out.println(cient.execute("5 * 2 + 9 - 1 / 6"));
        System.out.println(cient.execute("5 / 5 + 1 / 1 * 10 - 1"));
    }

    测试结果为:

        测试通过! 

4.4. 优化方向

    作为一个开发人员,完成一段代码功能之后当然要不断反思自己的代码还有哪些不足,就以上示例而言,针对使用者有可能的千奇百怪的输入来说,首先这段代码的异常捕获和提示内容就是不足的,当然,仅仅用来作为说明解释器模式的实例来讲,我们的目的已经达到了,所以这里不再多做补充(不是因为懒~)。另外,如果我们学习过其他设计模式,我们可以将解释器模式和其他设计模式结合使用,而达到进一步优化的目的,比如我们可以用组合模式来构建语法树,用工厂模式来创建不同类型的表达式,而如何通过代码来实现它们,就要涉及其他设计模式的说明了,本文暂不做赘述(那肯定也不是因为懒~)

五、总结

    解释器模式是一种强大而有趣的设计模式,它可以帮助我们简化复杂问题的处理过程。通过定义文法规则、创建抽象语法树和实现解释器,我们可以轻松地解释和执行特定语言的句子。希望本文对你理解解释器模式有所帮助,能够在实际开发中灵活运用!

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

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

相关文章

AI图像超分解决方案,还原更加清晰、逼真的画面

图像质量对于企业的业务运营和用户体验至关重要&#xff0c;许多场景下的图像分辨率不足&#xff0c;常常导致模糊效果不佳&#xff0c;难以满足企业的视觉需求。针对这一难题&#xff0c;美摄科技凭借其领先的AI技术&#xff0c;推出了创新的图像超分解决方案&#xff0c;为企…

【编程笔记】学会使用 Git

目录 一、介绍 Git二、安装 Git三、 常用 linux 目录四、Git 的必要配置(1) 查看和删除之前的配置(2) 配置 Git 五、Git 基本理论六、Git 项目搭建七、Git 文件操作八、分支Git 笔记 ❀❀❀(1) 常规使用(2) 分支 一、介绍 Git &#x1f4d6; VCS&#xff1a;Version Control S…

代码随想录-二叉树(路径)

目录 257. 二叉树的所有路径 题目描述&#xff1a; 输入输出描述&#xff1a; 思路和想法&#xff1a; 404. 左叶子之和 题目描述&#xff1a; 输入输出描述&#xff1a; 思路和想法&#xff1a; 513.找树左下角的值 题目描述&#xff1a; 输入输出描述&#xff1a;…

用JSch实现远程传输文件并打包成jar

本文将简单介绍一下 JSch 这个Java的第三方库的一个简单用法&#xff0c;并以此为实例&#xff0c;讲解 IntelliJ 中打包成 jar 包的2种方式。 实现目标 我们的目标是&#xff0c;做出一个jar包&#xff0c;它能够实现类似于 scp 命令的远程传输文件的功能。用法如下&#xf…

乡村数字化转型:科技赋能打造智慧农村新生态

随着信息技术的迅猛发展&#xff0c;数字化转型已成为推动社会进步的重要引擎。在乡村振兴的大背景下&#xff0c;乡村数字化转型不仅是提升乡村治理能力和治理水平现代化的关键&#xff0c;更是推动农业现代化、农村繁荣和农民增收的重要途径。本文旨在探讨乡村数字化转型的内…

MyBatis 初识简单操作

前言 上一期我们讲完Spring的配置文件以及日志的设置,这一期我们就来谈谈mybatis操作数据库的一些操作,使用这个框架可以极大地简化JDBC的冗长代码,大大增强了生产力,只需我们提供简单的sql语句以及对应的注解就可以操作数据库 我们说web应用程序主要分为三层 Controller Serv…

使用 golang 以及 Gin 框架,将上传的图片在不保存至本地的情况下添加水印,并上传至阿里云 OSS

正如标题所述&#xff0c;使用golang对上传图片添加水印&#xff0c;以及将图片上传到阿里云OSS&#xff0c;网上一搜索&#xff0c;便有你想要的结果了&#xff0c;可是&#xff0c;他们却先将上传图片添加水印后保存在本地&#xff0c;而后再将添加了水印的图片上传到阿里云O…

SRS OBS利用RTMP协议实现音视频推拉流

参考&#xff1a;https://ossrs.net/lts/zh-cn/docs/v5/doc/getting-started 1&#xff09;docker直接运行SRS服务&#xff1a; docker run --rm -it -p 1935:1935 -p 1985:1985 -p 8080:8080 registry.cn-hangzhou.aliyuncs.com/ossrs/srs:5运行起来后可以http://localho…

学习笔记——C语言基本概念指针(上)——(7)

今天学习了指针&#xff0c;指针吧理解有点小难&#xff0c;慢慢分析就懂。 在开始学指针之前先回顾一下C语言的数据类型如下图所示: 按照分类分别为&#xff1a; 1->基础数据类型&#xff1a;char &#xff1b;short&#xff1b; int&#xff1b; long&#xff1b; float&…

Platypus 一种集中式的央行数字货币方案

集中式的CBDC&#xff0c;混合使用账户模型和UTXO模型。 角色分类 中央银行&#xff1a;发行货币&#xff0c;交易验证&#xff0c;公开交易日志&#xff0c;防止双花。 不是完全受信任的&#xff0c;假定为会遵守监管要求&#xff0c;但可能会破坏交易隐私&#xff0c;即获…

瑞吉外卖实战学习--5、新增员工功能

新增员工功能 效果图1、开发流程2、页面发送ajax请求,将新增员工的信息以json的形式提交给服务器2.1、在填写信息的时候会发现身份校验比较麻烦,可以在validate中将全局的校验方式去掉,方便填写2.3、看到接口未employee2.4、前端代码分析3、服务器接收到提交的数据并调用ser…

无论PC还是Mac,都能畅快地使用移动硬盘 Mac使用NTFS移动硬盘不能读写

如果你拥有一台Mac设备&#xff0c;总会遇到尴尬的那一刻——你在Mac上用得好好的移动硬盘怎么都不能被PC识别到。又或者你朋友在PC上用得好好的移动硬盘&#xff0c;连上你的Mac后&#xff0c;Mac里的文件死活就是拷贝不进移动硬盘里。这种坑&#xff0c;相信大多数使用Mac的小…

Linux 基于chrony进行时钟同步方案验证

Linux 基于chrony进行时钟同步方案验证 1. 背景介绍2. 验证过程2.1 追踪配置2.2 追平记录2.2 追平时间换算 3. 疑问和思考3.1 如何统计追踪1s需要花费多长时间&#xff1f; 4. 参考文档 chrony是一个Linux系统中用于时钟同步的工具。它使用NTP&#xff08;网络时间协议&#xf…

【Java常用的API】JDK8相关时间类

&#x1f36c; 博主介绍&#x1f468;‍&#x1f393; 博主介绍&#xff1a;大家好&#xff0c;我是 hacker-routing &#xff0c;很高兴认识大家~ ✨主攻领域&#xff1a;【渗透领域】【应急响应】 【Java】 【VulnHub靶场复现】【面试分析】 &#x1f389;点赞➕评论➕收藏 …

Delphi 12 安卓 部署文件,不支持中文文件名

procedure TForm3.Button1Click(Sender: TObject); var sFileName:string; begin sFileName:TPath.Combine(TPath.GetDocumentsPath,禁止吸烟.wav); showmessage(sFileName); MediaPlayer1.Stop ; MediaPlayer1.FileName: sFileName; MediaPlayer1.Play; end;

c语言:vs2022写一个一元二次方程(包含虚根)

求一元二次方程 的根&#xff0c;通过键盘输入a、b、c&#xff0c;根据△的值输出对应x1和x2的值(保留一位小数)(用if语句完成)。 //一元二次方程的实现 #include <stdio.h> #include <math.h> #include <stdlib.h> int main() {double a, b, c, delta, x1…

商品说明书的制作工具来啦,用这几个就够了!

商品说明书是用户了解产品特性、性能、使用方法的重要途径。一个明确、易懂的商品说明书&#xff0c;可以显著提升用户体验&#xff0c;进而提升产品的销量。但我们都知道&#xff0c;制作一份高质量的说明书并不容易&#xff0c;需要仔细设计、计划和撰写。幸运的是&#xff0…

Python模块与包管理使用pip与virtualenv【第151篇—模块与包管理】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python模块与包管理&#xff1a;使用pip与virtualenv 在Python开发中&#xff0c;模块和包…

【Linux系列】tree和find命令

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…

网络性能提升10%,ZStack Edge 云原生超融合基于第四代英特尔®至强®可扩展处理器解决方案发布

随着业务模式的逐渐转变、业务架构逐渐变得复杂&#xff0c;同时容器技术的兴起和逐渐成熟&#xff0c;使得Kubernetes、微服务等新潮技术逐步应用于业务应用系统上。 为了充分释放性能、为业务系统提供更高效的运行环境&#xff0c;ZStack Edge 云原生超融合采用了第四代英特尔…