设计模式学习笔记(11)解释器

本文实例代码:https://github.com/JamesZBL/java_design_patterns

解释器(Interpreter)模式提供了校验语言的语法或表达式的途径,它属于行为型模式的一种。这种模式通常会提供一个表达式接口,通过这个接口可以解释对应特定环境的上下文。

解释器模式在日常开发的过程中不是很常用,但它在 SQL 解析、符号处理引擎、编译程序等场景中使用非常广泛。

实例

给定一个语言,解释器模式可以定义出其文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。举个简单的例子,在某种计算器中输入 5+1-3*2,每输入一个字符,屏幕上都会显示当前的结果,和这种计算器不同的是另外一种,即一次性输入 5+1-3*2 然后点击 =,直接得出最终结果,后面这种计算器就用到了解释器。

我们输入的这一系列符号可以用二叉树的形式来表示,比如 5+1-3*2

这个二叉树就是一个简单的语法树,在一般的计算机程序设计语言的编译过程中,通常都包含类似的语法树生成的过程。

图中的 5132 都叫做 终结符表达式,所谓终结符就是本身不能再推导出其他符号了,图中的 +-* 这些四则运算符号就是 非终结符表达式 了,因为可以由这些符号分别展开,形成各自的子表达式。对于四则运算表达式,解析的结果就是运算结果,所以运算符号可以抽象出一个接口,包含一个返回值为整数(假设只有整数参与运算)的 interpret() 方法。

Expression.java


ublic abstract class Expression {

  public abstract int interpret() throws Exception;

  @Override

  public abstract String toString();

}

由于加减乘除解释的出来的运算结果显然是不同的,所以分别实现这个接口形成四个类,四则运算符号需要左右两个操作数才能进行解释运算,所以每个运算符都持有两个符号的引用,分别作为其左运算数和右运算数:

加号,PlusExpression.java:


public class PlusExpression extends Expression {

  private Expression expressionLeft;

  private Expression expressionRight;

  public PlusExpression(Expression expressionLeft, Expression expressionRight) {

    this.expressionLeft = expressionLeft;

    this.expressionRight = expressionRight;

  }

  @Override

  public int interpret() throws Exception {

    return expressionLeft.interpret() + expressionRight.interpret();

  }

  @Override

  public String toString() {

    return "+";

  }

}

减号,MinusExpression.java:


public class MinusExpression extends Expression {

  private Expression expressionLeft;

  private Expression expressionRight;

  public MinusExpression(Expression expressionLeft, Expression expressionRight) {

    this.expressionLeft = expressionLeft;

    this.expressionRight = expressionRight;

  }

  @Override

  public int interpret() throws Exception {

    return expressionLeft.interpret() - expressionRight.interpret();

  }

  @Override

  public String toString() {

    return "-";

  }

}

乘号,MultipleExpression.java:


public class MultipleExpression extends Expression {

  private Expression expressionLeft;

  private Expression expressionRight;

  public MultipleExpression(Expression expressionLeft, Expression expressionRight) {

    this.expressionLeft = expressionLeft;

    this.expressionRight = expressionRight;

  }

  @Override

  public int interpret() throws Exception {

    return expressionLeft.interpret() * expressionRight.interpret();

  }

  @Override

  public String toString() {

    return "*";

  }

}

除号,DivisionExpression.java:


public class DivisionExpression extends Expression {

  private Expression expressionLeft;

  private Expression expressionRight;

  public DivisionExpression(Expression expressionLeft, Expression expressionRight) {

    this.expressionLeft = expressionLeft;

    this.expressionRight = expressionRight;

  }

  @Override

  public int interpret() throws Exception{

    return expressionLeft.interpret() / expressionRight.interpret();

  }

  @Override

  public String toString() {

    return "/";

  }

}

对于一个四则运算的算式,除了这四个四则运算符号,就是数字了,所以将数字抽象出一个数字符号类:

NumberExpression.java


public class NumberExpression extends Expression {

  private int number;

  public NumberExpression(int number) {

    this.number = number;

  }

  public NumberExpression(String numberString) {

    this.number = Integer.parseInt(numberString);

  }

  @Override

  public int interpret() {

    return number;

  }

  @Override

  public String toString() {

    return "数字";

  }

}

将上文中的算式用二叉树的形式表示,按顺序展开为一个字符序列,即 - + * 5 1 3 2,现在模仿计算器对其进行解释,这里用到了一点数据结构的知识,遍历二叉树通常采用 堆栈 (Stack) 结构来实现:

App.java


public class Application {

  private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);

  public static void main(String[] args) {

    try {

      String tokenString = "- + * 5 1 3 2";

      Stack<Expression> stack = new Stack<>();

      String[] stringList = tokenString.split(" ");

      for (String s : stringList) {

        if (isOperator(s)) {

          Expression expressionRight = stack.pop();

          Expression expressionLeft = stack.pop();

          LOGGER.info("左操作数:{},右操作数:{}", expressionLeft.interpret(), expressionRight.interpret());

          Expression expression = getExpressionInstance(s, expressionLeft, expressionRight);

          LOGGER.info("操作符:{}", expression);

          Expression result;

          if (expression != null) {

            result = new NumberExpression(expression.interpret());

            LOGGER.info("运算结果为:{}", result.interpret());

            stack.push(result);

          }

        } else {

          NumberExpression expression = new NumberExpression(s);

          stack.push(expression);

          LOGGER.info("数字入栈:{}", expression.interpret());

        }

      }

    } catch (Exception e) {

      e.printStackTrace();

    }

  }

  /**

   * 判断字符串是否为四则运算的操作符

   *

   * @param s 待判断的字符串

   *

   * @return 是否为操作符

   */

  public static boolean isOperator(String s) {

    return s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/");

  }

  /**

   * 根据字符串生成四则运算表达式

   *

   * @param s 字符串

   * @param expressionLeft 左表达式

   * @param expressionRight 右表达式

   *

   * @return 四则运算表达式

   */

  public static Expression getExpressionInstance(String s, Expression expressionLeft, Expression expressionRight) {

    if (isOperator(s)) {

      switch (s) {

        case "+": {

          return new PlusExpression(expressionLeft, expressionRight);

        }

        case "-": {

          return new MinusExpression(expressionLeft, expressionRight);

        }

        case "*": {

          return new MultipleExpression(expressionLeft, expressionRight);

        }

        case "/": {

          return new DivisionExpression(expressionLeft, expressionRight);

        }

      }

    }

    return null;

  }

}

总结

解释器主要运用方式就是解释语法树的节点,它将语法解释规则和实现结构,将复杂的解释工作指派到不同的解释器对象中,是什么语法就由什么解释器来解释。对于一棵生成好的语法树,通常由根节点开始解释,不断递归,依次选择适合子节点的解释器来进行解释。

解释器的应用场景:

  • 当一个语言需要解释执行,并可以将该语言中的句子表示为一个抽象语法树的时候,例如 XML 文件或正则表达式

  • 一些重复出现的问题可以用一种简单的语言来进行表达

  • 一个语言的文法较为简单,比如四则运算

  • 当执行效率不是关键和主要关心的问题时可考虑解释器模式,因为大量使用递归循环调用,随着语法的复杂程度加剧,解释器的执行效率会非常低

个人博客同步更新,获取更多技术分享请关注:郑保乐的博客

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,029评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,238评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,576评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,214评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,324评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,392评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,416评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,196评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,631评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,919评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,090评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,767评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,410评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,090评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,328评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,952评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,979评论 2 351

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • 一、Java 简介 Java是由Sun Microsystems公司于1995年5月推出的Java面向对象程序设计...
    子非鱼_t_阅读 4,173评论 1 44
  • 如果换个时间认识,结局会不会不一样。 "嘿,你过来,我有个事情想跟你谈谈。" "想跟我谈什么啊。" "有个恋爱想跟...
    北方小确幸阅读 345评论 0 5
  • 大家好,我是朵朵妈妈。每天给孩子们讲蓝色小象的故事,孩子们建议我把故事写出来,留着以后欣赏。所以今天鼓起勇气把自...
    steffi_tan阅读 810评论 1 0