基本需求

  • 实现四则运算,如计算a+b-c+d的值

    • 先输入表达式的形式,如a+b-c+d,要求表达式正确
    • 再分别输出a,b,c,d的值
    • 最后求出结果

传统方案

  • 编写一个方法,接收表达式的形式,根据用户输入的数值进行解析,得到结果
  • 如果加入新的运算符,比如*/(等等,不利于扩展,另外让一个方法解析会造成程序结构的混乱
  • 使用解释器模式,表达式 -> 解释器(多种) -> 结果

基本介绍

  • 在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做是解释器

  • 解释器模式(Interpreter)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在SQL解析、符号处理引擎等

  • 解释器模式指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)

  • 应用场景

    • 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
    • 一些重复出现的问题可以用一种简单的语言来表达
    • 一个简单语法需要解释的场景
    • 比如编译器、运算表达式计算、正则表达式等
  • UML类图(原理)

    • 说明

      • Context:是环境角色,含有解释器之外的全局信息
      • AbstractExpression:抽象表达式,声明一个抽象的解释器操作,这个方法为抽象语法树中所有的节点所共享
      • TerminalExpression:终结符表达式,实现与文法中的终结符相关的解释操作(相当于递归的出口)
      • NonTerminalExpression:非终结符表达式,为文法中的非终结符实现解释操作(需要聚合AbstractExpression,可能是TerminalExpression 或 NonTerminalExpression,最终构成一个递归结构)
      • Context 和 TerminalExpression 信息通过Client输入即可
  • UML类图(案例)

  • 构建解释器流程示意

  • 代码实现

    • // 抽象表达式类
      public abstract class AbstractExpression { // 声明一个解释操作 map的含义是作为终结者表达式获取值的方式 一般有Context提供,也就是递归的出口
      // 本次案例中 map的形式为{a=1,b=2,c=3...} key 为 表达式中字母 value为每个字母具体的值
      public abstract int interpreter (Map<String, Integer> map); }
    • // 变量表达式 此处作为了终结表达式 提供递归的出口
      public class VarExpression extends AbstractExpression { // 此处为key为表达式的字母
      private String key; public VarExpression(String key) {
      this.key = key;
      } @Override
      public int interpreter(Map<String, Integer> map) {
      // 根据Context提供的map 根据key 获取对应的值 作为递归的出口 例如 map.get("a") = 1
      return map.get(this.key);
      }
      }
    • // 符号表达式 为非终结表达式 此处仍然作为一个抽象类 因为符号有多种
      public abstract class SymbolExpression extends AbstractExpression{ // 聚合AbstractExpression 作为左表达式和右表示式 均为AbstractExpression下的某一子类
      // 左右表达式可能是终结表达式也可能是非终结表达式
      // 如果是终结表达式 作为递归的出口 直接在map中获取值,如果是非终结表达式 将map向下传递进行递归 直至递归出口
      protected AbstractExpression left; protected AbstractExpression right; public SymbolExpression(AbstractExpression left, AbstractExpression right) {
      this.left = left;
      this.right = right;
      }
      } // 子类一 减法表达式
      class SubExpression extends SymbolExpression { public SubExpression(AbstractExpression left, AbstractExpression right) {
      super(left, right);
      } // 实现减法操作 左表达式的值 - 右表达式的值
      @Override
      public int interpreter(Map<String, Integer> map) {
      return this.left.interpreter(map) - this.right.interpreter(map);
      }
      } // 子类二 加法表达式
      class AddExpression extends SymbolExpression { public AddExpression(AbstractExpression left, AbstractExpression right) {
      super(left, right);
      } // 实现加法操作 左表达式的值 + 右表达式的值
      @Override
      public int interpreter(Map<String, Integer> map) {
      return this.left.interpreter(map) + this.right.interpreter(map);
      }
      } // 如需*/等 直接加子类即可
    • // 计算器类 此处作为Context环境类 向解释器提供环境
      public class Calculator { private AbstractExpression abstractExpression; public Calculator(String expression) {
      // 使用栈 构建解释器 递归形式 始终保持栈中只会有一个元素(但嵌套了很多AbstractExpression)
      Stack<AbstractExpression> abstractExpressionStack = new Stack<>();
      char[] chars = expression.toCharArray();
      AbstractExpression left = null;
      AbstractExpression right = null;
      for (int i = 0; i < chars.length; i++) {
      switch (chars[i]) {
      case '+' :
      // 是加法 在栈中取出左表达式 创建右表达式 构建AddExpression入栈
      left = abstractExpressionStack.pop();
      right = new VarExpression(String.valueOf(chars[++i]));
      abstractExpressionStack.push(new AddExpression(left, right));
      break;
      case '-' :
      // 是减法 在栈中取出左表达式 创建右表达式 构建SubExpression入栈
      left = abstractExpressionStack.pop();
      right = new VarExpression(String.valueOf(chars[++i]));
      abstractExpressionStack.push(new SubExpression(left, right));
      break;
      default:
      // 是字母 直接构建VarExpression 并入栈 作为递归的出口
      abstractExpressionStack.push( new VarExpression(String.valueOf(chars[i])));
      break;
      }
      }
      // 在栈中取出构建好的表达式并赋值给成员变量
      this.abstractExpression = abstractExpressionStack.pop();
      } // 使用Context中的环境进行解释(也就是计算表达式的值)
      public int run(Map<String, Integer> map) {
      return this.abstractExpression.interpreter(map);
      }
      }
    • public class Client {
      public static void main(String[] args) {
      System.out.println("请输入表达式:");
      Scanner scanner = new Scanner(System.in);
      String expr = scanner.nextLine();
      // 表达式的数组
      char[] chars = expr.toCharArray();
      // 创建Map用于给解释器提供参数
      Map<String, Integer> map = new HashMap<>();
      for (char aChar : chars) {
      String value;
      if ('+' != aChar && '-' != aChar) {
      if (!map.containsKey(String.valueOf(aChar))) {
      System.out.println("请输入" + aChar + "的值:");
      value = scanner.nextLine();
      map.put(String.valueOf(aChar), Integer.valueOf(value));
      }
      }
      }
      // 使用计算器 进行计算 将构建好的map通过Context传递给解释器
      Calculator calculator = new Calculator(expr);
      System.out.println("表达式" + expr + "的计算结果是:" + calculator.run(map));
      }
      }

spring源码

  • 在spring中的SpelExpressionParse中就使用到了解释器模式

  • 测试代码

    • public class SpelExpressionParseTest {
      public static void main(String[] args) {
      SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
      Expression expression = spelExpressionParser.parseExpression("(5 + 5) * 6");
      Integer value = (Integer) expression.getValue();
      System.out.println(value);
      }
      }
  • UML类图

    • 说明

      • TemplateAwareExpressionParser为ExpressionParser接口的实现类,实现了parseExpression方法,该方法中调用了parseTemplate方法和doParseExpression方法
      • parseTemplate方法根据参数返回不同的解释器LiteralExpression和CompositeStringExpression
      • 而doParseExpression方法为抽象方法,其实现在子类SpelParseExpression和InternalSpelExpressionParse中,其返回值都是SpelExpression
      • Expression接口为解释器接口,其中有三个实现类,其中的getValue方法为解释方法

注意事项

  • 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
  • 使用解释器可能会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能会降低

20.java设计模式之解释器模式的更多相关文章

  1. 折腾Java设计模式之解释器模式

    解释器模式 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器.客户端可以使用这个解释器来解释这个语言中的句子. 意图 给定一个语言,定义它的文法表 ...

  2. JAVA设计模式之解释器模式

    在阎宏博士的<JAVA与模式>一书中开头是这样描述解释器(Interpreter)模式的: 解释器模式是类的行为模式.给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个 ...

  3. 简单的介绍一下Java设计模式:解释器模式

    目录 定义 意图 主要解决问题 优缺点 结构 示例 适用情况 定义 解释器模式是类的行为型模式,给定一个语言之后,解释器模式可以定义出其文法的一种表示,并同时提供一个解释器,客户端可以使用这个解释器来 ...

  4. java设计模式5--原型模式(Prototype)

    本文地址:http://www.cnblogs.com/archimedes/p/java-prototype-pattern.html,转载请注明源地址. 原型模式 用原型实例指定创建对象的种类,并 ...

  5. 【设计模式】Java设计模式 - 装饰者模式

    Java设计模式 - 装饰者模式 不断学习才是王道 继续踏上学习之路,学之分享笔记 总有一天我也能像各位大佬一样 原创作品,更多关注我CSDN: 一个有梦有戏的人 准备将博客园.CSDN一起记录分享自 ...

  6. Java设计模式——装饰者模式

    JAVA 设计模式 装饰者模式 用途 装饰者模式 (Decorator) 动态地给一个对象添加一些额外的职责.就增加功能来说,Decorator 模式相比生成子类更为灵活. 装饰者模式是一种结构式模式 ...

  7. 浅析JAVA设计模式之工厂模式(一)

    1 工厂模式简单介绍 工厂模式的定义:简单地说,用来实例化对象,取代new操作. 工厂模式专门负责将大量有共同接口的类实例化.工作模式能够动态决定将哪一个类实例化.不用先知道每次要实例化哪一个类. 工 ...

  8. 乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern)

    原文:乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern) [索引页][源码下载] 乐在其中设计模式(C#) - 解释器模式(Interpreter Pattern) 作 ...

  9. JAVA设计模式--装饰器模式

    装饰器模式 装饰器模式(Decorator Pattern)允许向一个现有的对象添加新的功能,同时又不改变其结构.这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装. 这种模式创建了一个装饰 ...

随机推荐

  1. 容器编排系统之K8s资源标签、标签选择器、资源注解

    前文我们聊到了使用k8s资源配置清单定义资源的相关话题,回顾请参考https://www.cnblogs.com/qiuhom-1874/p/14132890.html:今天我们来聊下资源标签,标签选 ...

  2. win10平衡模式、高性能模式和卓越模式三种电池模式的区别

    win10在1803版本后,有了很多隐藏的功能.电池模式中的"卓越模式"就是其中之一. 互相比较一下: 节能模式:顾名思义是最省电的,此模式下会禁用一些系统特效,且CPU运行频率是 ...

  3. 使用plsql 连接oracle数据库

    1. 首先,需要下载oracle数据库的客户端,因为plsql是32位的,所以推荐下载32位的oracle数据库客户端呢 2. 将客户端解压到没有中文目录的文件夹下. 3. 在plsql中指定orac ...

  4. js上 四、数据类型转换

    1. 转布尔类型 Boolean():可以将任意类型的数据转为布尔类型: 语法:Boolean(值) 规则:

  5. numpy和pandas-数据分析模块

    应用:1.数据分析 2.深度学习 3.机器学习 运算速度快:numpy 和 pandas 都是采用 C 语言编写, pandas 又是基于 numpy, 是 numpy 的升级版本. 消耗资源少:采用 ...

  6. JavaScript之作用域-作用域链

    作用域 ==> 作用域链   作用域:变量可以其作用的区域(声明定义好一个变量,变量可以在哪些范围内使用) 分类:全局作用域和局部作用域(函数作用域):在js中,目前全局有作用域以及函数可以形成 ...

  7. sql 查询条件为拼接字符串 不能使用IN 使用patindex查询结果集

    题目: 求组织机构ID在('5dc8de20-9f2f-465e-afcc-f69abecaee50','63549b63-1e0d-4269-98f4-013869d7f211','f7316bf3 ...

  8. java中远程调用接口springboot

    package com.kakarote.crm.utils; import cn.hutool.core.util.ObjectUtil; import org.apache.http.client ...

  9. Redis基础篇(五)AOF与RDB比较和选择策略

    RDB和AOF对比 关于RDB和AOF的优缺点,官网上面也给了比较详细的说明redis.io/topics/pers- RDB 优点: RDB快照是一个压缩过的非常紧凑的文件,保存着某个时间点的数据集 ...

  10. Qt学习笔记-制作一个计算器-对话框Message Box

    在做计算器的前提先做一个加法器. 设计界面. 在点击计算的时候,获取前两个输入框中的数据相加后显示在第三个输入框. toInt是将字符串转换为数字.number静态函数是将数字转化为字符串. 加法器已 ...