1. 程式人生 > >九、直譯器設計模式

九、直譯器設計模式

1.介紹

直譯器模式是類的行為模式,給定一個語言後,直譯器模式可以定義出其文法的一種表示,並同時提供一個直譯器。客戶端可以使用這個直譯器來解釋這個語言中的句子。

直譯器模式在實際運用上相對來說少很多,因為很少會自己去構造語言的文法。

2.直譯器模式使用場景

  • 重複發生的問題可以使用直譯器模式。
  • 一個簡單語法需要解釋的場景。

3. 直譯器模式的UML類圖

直譯器模式UML

由於直譯器模式很好使用,下面的術語解釋直接複製網路(侵刪)

  • 直譯器模式的結構

抽象直譯器:

宣告一個所有具體表達式都要實現的抽象介面(或者抽象類),介面中主要是一個interpret()方法,稱為解釋操作。
具體解釋任務由它的各個實現類來完成,具體的直譯器分別由終結符直譯器TerminalExpression和非終結符直譯器NonterminalExpression完成。

終結符表示式:

實現與文法中的元素相關聯的解釋操作,通常一個直譯器模式中只有一個終結符表示式,但有多個例項,對應不同的終結符。終結符一半是文法中的運算單元,比如有一個簡單的公式R=R1+R2,在裡面R1和R2就是終結符,對應的解析R1和R2的直譯器就是終結符表示式。

非終結符表示式:

文法中的每條規則對應於一個非終結符表示式,非終結符表示式一般是文法中的運算子或者其他關鍵字,比如公式R=R1+R2中,+就是非終結符,解析+的直譯器就是一個非終結符表示式。非終結符表示式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結符表示式。

環境角色:

這個角色的任務一般是用來存放文法中各個終結符所對應的具體值,比如R=R1+R2,我們給R1賦值100,給R2賦值200。這些資訊需要存放到環境角色中,很多情況下我們使用Map來充當環境角色就足夠了。

4.直譯器模式簡單實現

  • 抽象的算術運算直譯器 ArithmeticExpression
public abstract class ArithmeticExpression {

            /**
             * 抽象的解析方法
             * 具體的解析邏輯由具體子類實現
             * @return
             */
            public abstract int interpret();
}
  • 數字直譯器
public class NumExpression extends ArithmeticExpression
{
private int num; public NumExpression(int num) { this.num = num; } @Override public int interpret() { return num; } }
  • 運算子號抽象直譯器,為所有運算子號直譯器共性的提取
public class OperationExpression extends ArithmeticExpression {
            //宣告兩個成員變數儲存運算子號兩邊的數字直譯器
            protected ArithmeticExpression exp1, exp2;

            public OperationExpression(ArithmeticExpression exp1, ArithmeticExpression exp2) {
                this.exp1 = exp1;
                this.exp2 = exp2;
            }

            @Override
            public int interpret() {
                return 0;
            }
}
  • 加法運算抽象直譯器
public class AdditionExpression extends OperationExpression {
            public AdditionExpression(ArithmeticExpression exp1, ArithmeticExpression exp2) {
                super(exp1, exp2);
            }

            @Override
            public int interpret() {
                return exp1.interpret() + exp2.interpret();
            }
        }
  • 處理與解釋相關的一些業務:
public class Calculator {
            //宣告一個Stack棧儲存並操作所有相關的直譯器
            private Stack<ArithmeticExpression> expressions = new Stack<ArithmeticExpression>();

            public Calculator(String expression) {
                //宣告兩個ArithmeticExpressioin型別的臨時變數,儲存運算子左右兩邊的數字直譯器
                ArithmeticExpression exp1, exp2;

                String[] elements = expression.split(" ");

                for (int i = 0; i < elements.length; i++) {
                    switch (elements[i].charAt(0)) {
                        case '+':
                            //將棧中的直譯器彈出作為運算子號左邊的直譯器
                            exp1 = expressions.pop();
                            //同時將運算子號陣列角標下一個元素構成
                            exp2 = new NumExpression(Integer.valueOf(elements[++i]));
                            //通過上面兩個數字直譯器構造加法運算直譯器
                            expressions.push(new AdditionExpression(exp1, exp2));
                            break;
                        default://如果為數字
                            /**
                             * 如果不是運算子則數字
                             * 則是數字,直接構造數字直譯器並壓入棧
                             */
                            expressions.push(new NumExpression(Integer.valueOf(elements[i])));
                            break;
                    }
                }
            }

            /**
             * 計算
             * @return
             */
            public int calculate() {
                return expressions.pop().interpret();
            }
        }
  • 測試類:
public class Client {
            public static void main(String[] args) {

                Calculator calculator = new Calculator("100 + 30 + 5");
                System.out.println(calculator.calculate());
            }
}

這裡簡單說一下Calculator裡面的流程,以為”100 + 30 +5”為例:

  1. 首先將其拆分為五個元素組成的字串陣列。

  2. 迴圈遍歷陣列,首先遍歷到的是100,那麼將其構造成一個NumExpression物件壓入棧。

  3. 接著遍歷到了加號運算子,此時將我們剛剛壓入棧的100出棧,作為加號運算子左邊的數字直譯器

  4. 接著,將當前加號運算子下一個角標構造成一個數字直譯器,作為加號運算子右邊的數字直譯器

  5. 將左右兩個數字直譯器作為引數傳入AdditionExpression構造一個加法直譯器,同時壓入棧中。

  6. 重複步驟3的操作

  7. 重複步驟4的操作。

  8. 最後呼叫interpret(),結果相當於exp1.interpret() + exp2.interpret()+exp3.interpret()

5.直譯器模式在Android原始碼中的實現

直譯器模式在Android原始碼中並不常見,這裡就不做具體分析了。

6. 總結:

  • 優點:
    • 靈活的擴充套件性,當我們想對文法規則進行擴充套件時,只需要增加相應的非終結符號直譯器,並在構建抽象語法樹時,使用到新增的直譯器物件進行具體的解釋即可,非常方便。
  • 缺點:
    • 過於複雜的語法,構建其抽象語法樹會顯得異常繁瑣,甚至有可能出現需要構建多棵抽象語法樹的情況,因此,對於複雜的文法並不推薦使用直譯器模式。
    • 對於每一條文法都可以對應至少一個直譯器,其會生成大量的類,導致後期維護困難。