基本需求

  • 实现四则运算,如计算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. 密码管理平台ratticdb的部署,在centos7上的部署

    一,前言 一直想用ratticdb这个有web界面的密码管理工具,百度了一下居然没有找到中文的部署文档,访问官网也是notfound.找到了官方的部署指南:https://github.com/til ...

  2. Hive JDBC执行load时无法从本地加载数据

    通过hive-jdcv连接hive server,在应用服务端执行以下命令,报错:Hiver Server节点上找不到data.txt load data local inpath '/home/dw ...

  3. html 03-初识HTML

    03-初识HTML #本文主要内容 头标签 排版标签:<p>. <div>. <span>.<br> . <hr> . <center ...

  4. Python读写EXCEL文件常用方法大全

    前言 python读写excel的方式有很多,不同的模块在读写的讲法上稍有区别,这里我主要介绍几个常用的方式. 用xlrd和xlwt进行excel读写: 用openpyxl进行excel读写: 用pa ...

  5. Flink读取resource资源文件

    public static ParameterTool getParameterTool(String[] args){ try { return ParameterTool .fromPropert ...

  6. javaScript继承的几种实现方式?

    js继承总共分成5种,包括构造函数式继承.原型链式继承.组合式继承.寄生式继承和寄生组合式继承. 构造函数式继承 首先来看第一种,构造函数式继承,顾名思义,也就是利用函数去实现继承:构造函数继承,使用 ...

  7. SQLServer之 Stuff和For xml path

    示例 昨天遇到一个SQL Server的问题:需要写一个储存过程来处理几个表中的数据,最后问题出在我想将一个表的一个列的多行内容拼接成一行,比如表中有两列数据 : 类别 名称 AAA 企业1 AAA ...

  8. [AspNetCore3.1] 使用Serilog记录日志

    用到的单词 Sink 接收器模块.输出方式.接收模块库.输出模块库 Diagnostic 诊断 Enricher 扩展器 embedded 嵌入式的 compact 紧凑的.简洁的 concept 概 ...

  9. VS挂接崩溃包

    主要用来在用户机器上对目标进程生成dump文件,定位"卡死".Crash等问题.推荐相关工具DumpTool,WinCrashReport. DumpTool 下载 WinCras ...

  10. maven项目修改名称后,打包名称和现在名称不一致

    将pom.xm文件中 <artifactId>health</artifactId> 修改成现在项目名称,然后 maven clean ->maven install 如 ...