設(shè)計(jì)模式學(xué)習(xí)筆記(11)解釋器

本文實(shí)例代碼:https://github.com/JamesZBL/java_design_patterns

解釋器(Interpreter)模式提供了校驗(yàn)語言的語法或表達(dá)式的途徑,它屬于行為型模式的一種。這種模式通常會(huì)提供一個(gè)表達(dá)式接口,通過這個(gè)接口可以解釋對(duì)應(yīng)特定環(huán)境的上下文。

解釋器模式在日常開發(fā)的過程中不是很常用,但它在 SQL 解析、符號(hào)處理引擎、編譯程序等場景中使用非常廣泛。

實(shí)例

給定一個(gè)語言,解釋器模式可以定義出其文法的一種表示,并定義一個(gè)解釋器,該解釋器使用該表示來解釋語言中的句子。舉個(gè)簡單的例子,在某種計(jì)算器中輸入 5+1-3*2,每輸入一個(gè)字符,屏幕上都會(huì)顯示當(dāng)前的結(jié)果,和這種計(jì)算器不同的是另外一種,即一次性輸入 5+1-3*2 然后點(diǎn)擊 =,直接得出最終結(jié)果,后面這種計(jì)算器就用到了解釋器。

我們輸入的這一系列符號(hào)可以用二叉樹的形式來表示,比如 5+1-3*2

這個(gè)二叉樹就是一個(gè)簡單的語法樹,在一般的計(jì)算機(jī)程序設(shè)計(jì)語言的編譯過程中,通常都包含類似的語法樹生成的過程。

圖中的 51、 32 都叫做 終結(jié)符表達(dá)式,所謂終結(jié)符就是本身不能再推導(dǎo)出其他符號(hào)了,圖中的 +、 -、 * 這些四則運(yùn)算符號(hào)就是 非終結(jié)符表達(dá)式 了,因?yàn)榭梢杂蛇@些符號(hào)分別展開,形成各自的子表達(dá)式。對(duì)于四則運(yùn)算表達(dá)式,解析的結(jié)果就是運(yùn)算結(jié)果,所以運(yùn)算符號(hào)可以抽象出一個(gè)接口,包含一個(gè)返回值為整數(shù)(假設(shè)只有整數(shù)參與運(yùn)算)的 interpret() 方法。

Expression.java


ublic abstract class Expression {

  public abstract int interpret() throws Exception;

  @Override

  public abstract String toString();

}

由于加減乘除解釋的出來的運(yùn)算結(jié)果顯然是不同的,所以分別實(shí)現(xiàn)這個(gè)接口形成四個(gè)類,四則運(yùn)算符號(hào)需要左右兩個(gè)操作數(shù)才能進(jìn)行解釋運(yùn)算,所以每個(gè)運(yùn)算符都持有兩個(gè)符號(hào)的引用,分別作為其左運(yùn)算數(shù)和右運(yùn)算數(shù):

加號(hào),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 "+";

  }

}

減號(hào),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 "-";

  }

}

乘號(hào),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 "*";

  }

}

除號(hào),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 "/";

  }

}

對(duì)于一個(gè)四則運(yùn)算的算式,除了這四個(gè)四則運(yùn)算符號(hào),就是數(shù)字了,所以將數(shù)字抽象出一個(gè)數(shù)字符號(hào)類:

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 "數(shù)字";

  }

}

將上文中的算式用二叉樹的形式表示,按順序展開為一個(gè)字符序列,即 - + * 5 1 3 2,現(xiàn)在模仿計(jì)算器對(duì)其進(jìn)行解釋,這里用到了一點(diǎn)數(shù)據(jù)結(jié)構(gòu)的知識(shí),遍歷二叉樹通常采用 堆棧 (Stack) 結(jié)構(gòu)來實(shí)現(xiàn):

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("左操作數(shù):{},右操作數(shù):{}", 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("運(yùn)算結(jié)果為:{}", result.interpret());

            stack.push(result);

          }

        } else {

          NumberExpression expression = new NumberExpression(s);

          stack.push(expression);

          LOGGER.info("數(shù)字入棧:{}", expression.interpret());

        }

      }

    } catch (Exception e) {

      e.printStackTrace();

    }

  }

  /**

   * 判斷字符串是否為四則運(yùn)算的操作符

   *

   * @param s 待判斷的字符串

   *

   * @return 是否為操作符

   */

  public static boolean isOperator(String s) {

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

  }

  /**

   * 根據(jù)字符串生成四則運(yùn)算表達(dá)式

   *

   * @param s 字符串

   * @param expressionLeft 左表達(dá)式

   * @param expressionRight 右表達(dá)式

   *

   * @return 四則運(yùn)算表達(dá)式

   */

  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;

  }

}

總結(jié)

解釋器主要運(yùn)用方式就是解釋語法樹的節(jié)點(diǎn),它將語法解釋規(guī)則和實(shí)現(xiàn)結(jié)構(gòu),將復(fù)雜的解釋工作指派到不同的解釋器對(duì)象中,是什么語法就由什么解釋器來解釋。對(duì)于一棵生成好的語法樹,通常由根節(jié)點(diǎn)開始解釋,不斷遞歸,依次選擇適合子節(jié)點(diǎn)的解釋器來進(jìn)行解釋。

解釋器的應(yīng)用場景:

  • 當(dāng)一個(gè)語言需要解釋執(zhí)行,并可以將該語言中的句子表示為一個(gè)抽象語法樹的時(shí)候,例如 XML 文件或正則表達(dá)式

  • 一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)

  • 一個(gè)語言的文法較為簡單,比如四則運(yùn)算

  • 當(dāng)執(zhí)行效率不是關(guān)鍵和主要關(guān)心的問題時(shí)可考慮解釋器模式,因?yàn)榇罅渴褂眠f歸循環(huán)調(diào)用,隨著語法的復(fù)雜程度加劇,解釋器的執(zhí)行效率會(huì)非常低

個(gè)人博客同步更新,獲取更多技術(shù)分享請(qǐng)關(guān)注:鄭保樂的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 第2章 基本語法 2.1 概述 基本句法和變量 語句 JavaScript程序的執(zhí)行單位為行(line),也就是一...
    悟名先生閱讀 4,503評(píng)論 0 13
  • 一、Java 簡介 Java是由Sun Microsystems公司于1995年5月推出的Java面向?qū)ο蟪绦蛟O(shè)計(jì)...
    子非魚_t_閱讀 4,538評(píng)論 1 44
  • 如果換個(gè)時(shí)間認(rèn)識(shí),結(jié)局會(huì)不會(huì)不一樣。 "嘿,你過來,我有個(gè)事情想跟你談?wù)劇?quot; "想跟我談什么啊。" "有個(gè)戀愛想跟...
    北方小確幸閱讀 403評(píng)論 0 5
  • 大家好,我是朵朵媽媽。每天給孩子們講藍(lán)色小象的故事,孩子們建議我把故事寫出來,留著以后欣賞。所以今天鼓起勇氣把自...
    steffi_tan閱讀 883評(píng)論 1 0

友情鏈接更多精彩內(nèi)容