1. 程式人生 > 實用技巧 >20.java設計模式之直譯器模式

20.java設計模式之直譯器模式

基本需求

  • 實現四則運算,如計算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方法為解釋方法

注意事項

  • 當有一個語言需要解釋執行,可將該語言中的句子表示為一個抽象語法樹,就可以考慮使用直譯器模式,讓程式具有良好的擴充套件性
  • 使用直譯器可能會引起類膨脹、直譯器模式採用遞迴呼叫方法,將會導致除錯非常複雜、效率可能會降低