JAVA设计模式之解释器模式详解

news2024/11/28 19:03:23

解释器模式

1 解释器模式介绍

解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、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" 可以通过下面的抽象语法树表示

在这里插入图片描述

2 解释器模式原理

在这里插入图片描述

解释器模式包含以下主要角色。

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

3 解释器模式实现

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

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

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

  • 比如“ 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();
   }
}

代码重构

上面代码的所有的解析逻辑都耦合在一个函数中,这样显然是不合适的。这 个时候,我们就要考虑拆分代码,将解析逻辑拆分到独立的小类中, 前面定义的语法规则有两类表达式,一类是数字,一类是运算符,运算符又包括加减乘除。 利用解释器模式,我们把解析的工作拆分到以下五个类: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);
    }

    @Override
    public 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;
    }

    @Override
    public 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;
    }

    @Override
    public long interpret() {
        return exp1.interpret() - exp2.interpret();
    }
}

/**
 * 乘法运算
 * @author spikeCong
 **/
public class MulExpression implements Expression {

    private Expression exp1;
    private Expression exp2;

    public MulExpression(Expression exp1, Expression exp2) {
        this.exp1 = exp1;
        this.exp2 = exp2;
    }

    @Override
    public 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;
    }

    @Override
    public 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);
    }
}

4 解释器模式总结

  1. 解释器优点
  • 易于改变和扩展文法

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

  • 实现文法比较容易

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

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

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

  1. 解释器缺点
  • 对于复杂文法难以维护

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

  • 执行效率低

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

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

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

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

相关文章

零基础学编程怎么入手,中文编程工具构件箱之星空构件用法教程,系统化的编程视频教程上线

零基础学编程怎么入手&#xff0c;中文编程工具构件箱之星空构件用法教程&#xff0c;系统化的编程视频教程上线 一、前言 今天给大家分享的中文编程开发语言工具资料如下&#xff1a; 编程入门视频教程链接 http://​ https://edu.csdn.net/course/detail/39036 ​ 编程…

Java17之使用Lambda表达式对对象集合中指定的字段进行排序

Java17之使用Lambda表达式对对象集合中指定的字段进行排序 文章目录 Java17之使用Lambda表达式对对象集合中指定的字段进行排序1. 集合对象排序1. Java实体类2. 正序排序3.倒序排序 1. 集合对象排序 Java8起可用 List 的 sort 方法进行排序&#xff0c;形参为函数式接口Compara…

C++ //练习 6.12 改写6.2.1节中练习6.10(第188页)的程序,使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢?为什么?

C Primer&#xff08;第5版&#xff09; 练习 6.12 练习 6.12 改写6.2.1节中练习6.10&#xff08;第188页&#xff09;的程序&#xff0c;使用引用而非指针交换两个整数的值。你觉得哪种方法更易于使用呢&#xff1f;为什么&#xff1f; 环境&#xff1a;Linux Ubuntu&#x…

Docker关于conda环境的导出和导入

Docker关于conda环境的导出和导入 1、常用命令&#xff1a;2、环境导出&#xff1a;3、两个服务器之间的文件传输命令&#xff1a;4、环境导入&#xff1a;5、快速进入容器6、其他问题解决记录&#xff1a; 1、常用命令&#xff1a; docker pull -- 从远程仓库将镜像下载至本地…

医疗相关名词,医疗名词整理

1.系统类&#xff1a; HIS Hospital Information System&#xff0c;医院信息系统&#xff0c;在国际学术界已公认为新兴的医学信息学(Medical Informatics)的重要分支。美国该领域的著名教授Morris.Collen于1988年曾著文为医院信息系统下了如下定义&#xff1a;利用电子计算…

[Linux开发工具]项目自动化构建工具-make/Makefile

&#x1f4d9; 作者简介 &#xff1a;RO-BERRY &#x1f4d7; 学习方向&#xff1a;致力于C、C、数据结构、TCP/IP、数据库等等一系列知识 &#x1f4d2; 日后方向 : 偏向于CPP开发以及大数据方向&#xff0c;欢迎各位关注&#xff0c;谢谢各位的支持 目录 1.背景2.依赖关系和依…

error An unexpected error occurred: “https://registry.npm.taobao.org

背景&#xff1a; 想使用yarn命令结果报错 问题原因&#xff1a; 原来证书到期了 http://registry.npm.taobao.org/ 把这个放到浏览器搜索的时候自动换成https://registry.npmmirror.com/ 方案&#xff1a; npm cache clean --forcenpm config set registry https://registry…

Windows 虚拟桌面信息(一)分析注册表

目录 前言 一、理论分析 二、代码实现 总结 本文为原创文章&#xff0c;转载请注明出处&#xff1a; https://blog.csdn.net/qq_59075481/article/details/136110636 前言 Win 10/11 的虚拟桌面微软暂时没有开放接口&#xff0c;有很多信息对开发者是闭塞的&#xff0c;…

Qt QWidget以及各种控件、布局 核心属性(适合入门使用时查询)

目录 1. QWidget核心属性 2. 按钮类控件 2.1 PushButton 核心属性 2.2 RadioButton 核心属性 2.3 CheckBox 和 Tool Button 核心属性 3. 显示类控件 3.1 Label 核心属性 3.2 LCDNumber 核心属性 3.3 ProgressBar 核心属性 3.4 Calendar Widget 核心属性 4. 输入类控…

Photoshop 中的“彩蛋”

在 Photoshop 中隐藏了几个“彩蛋” Easter Eggs&#xff0c;是开发者留下的小秘密或玩笑功能&#xff0c;也许是他们在紧张的开发过程中的一种自我调节吧&#xff0c;就如复活节彩蛋一样&#xff0c;同样也可以给 Photoshop 的用户们带来一点小“惊喜”。 这些彩蛋通常以有趣的…

上位机图像处理和嵌入式模块部署(借鉴与学习)

【 声明&#xff1a;版权所有&#xff0c;欢迎转载&#xff0c;请勿用于商业用途。 联系信箱&#xff1a;feixiaoxing 163.com】 对于很多学院派的同学来说&#xff0c;他们对市场的感觉一般是比较弱的。如果写一个软件的话&#xff0c;或者说开发一个项目的话&#xff0c;他们…

【C++关联式容器】unordered_map

目录 unordered_map 1. pair类型 2. 关联式容器额外的类型别名 3. 哈希桶 4. 无序容器对关键字类型的要求 5. Member functions 5.1 constructor、destructor、operator 5.1.1 constructor 5.1.2 destructor 5.1.3 operator 5.2 Capacity ​5.2.1 empty 5.2.2 si…

C/C++内存管理:new、delete功能及原理实现

目录 一、C/C内存分布 二、C中内存管理方式 2.1new/delete操作内置类型 2.2 new和delete操作自定义类型 三、operator new与operator delete函数 四、new和delete的实现原理 4.1内置类型 4.2自定义类型 五、定位new 一、C/C内存分布 int globalVar 1; static int sta…

Java学习第十三节之三种初始化和内存分析

三种初始化 package array;public class ArrayDemo02 {public static void main(String[] args) {//静态初始化&#xff1b;创建赋值int[] a {1, 2, 3, 4, 5, 6, 7, 8};System.out.println(a[0]);for (int i 0; i <a.length; i) {System.out.println(a[i]);}//动态初始化…

你的电脑关机吗

目录 程序员为什么不喜欢关电脑&#xff1f; 电脑长时间不关机会怎样? 电脑卡顿 中度风险 硬件损耗 能源浪费 散热问题 软件问题 网络安全问题 程序员为什么不喜欢关电脑&#xff1f; 大部分人都会选择将电脑进行关机操作。其实这不难理解&#xff0c;毕竟人类都需要…

Qt初使用(使用Qt创建项目,在创建的项目中添加类,Qt中输出内容到控制台,设置窗口大小和窗口标题,Qt查看说明文档)

目录 一.创建带模板的项目新建项目运行在文件中查看该项目文件 二.在创建好的项目中添加类三.创建空项目&#xff08;不使用自带的模板&#xff09;四.Qt中输出内容到控制台五.设置窗口大小 , 窗口标题 ,固定窗口大小QWidget组件的说明 六.Pro文件帮助文档 按windows键&#xf…

1_opencv3环境搭建与测试

之前2020年5月写过一次&#xff0c;时隔3年多&#xff0c;有机会再重新写一次。相比之前&#xff0c;应该是有一点儿进步的。之前是使用默认安装路径&#xff0c;所以无需配置共享库的搜索路径。这次是自定义安装路径&#xff0c;略有区别。随着写程序的时间增长&#xff0c;编…

LeetCode Python - 14.最长公共前缀

目录 题目答案运行结果 题目 编写一个函数来查找字符串数组中的最长公共前缀。 如果不存在公共前缀&#xff0c;返回空字符串 “”。 示例 1&#xff1a; 输入&#xff1a;strs [“flower”,“flow”,“flight”] 输出&#xff1a;“fl” 示例 2&#xff1a; 输入&#xff…

react 【二】 setState/react性能优化/dom操作

文章目录 1、setState1.1 setState的三种用法1.2 setState为什么是异步 2、React性能优化2.1 react的更新机制2.2 如何优化性能2.2.1 shouldComponentUpdate2.2.2 PureComponent2.2.3 memo 3、不可变数据的力量4、dom操作4.1 通过ref获取dom的三种方式4.2 执行子组件的方法&…

关于node与node-sass那些事

昨晚找了之前的一个项目想要复习下&#xff0c;结果npm i报错&#xff0c;大致意思就是noda-sass的版本和node的对不上&#xff0c;那怎么办呢&#xff1a; 1.换node版本&#xff0c;那好吧&#xff0c;首先要明白&#xff0c;对应的版本关系 2.然后我开始用nvm换node版本&am…