九、直譯器設計模式
1.介紹
直譯器模式是類的行為模式,給定一個語言後,直譯器模式可以定義出其文法的一種表示,並同時提供一個直譯器。客戶端可以使用這個直譯器來解釋這個語言中的句子。
直譯器模式在實際運用上相對來說少很多,因為很少會自己去構造語言的文法。
2.直譯器模式使用場景
- 重複發生的問題可以使用直譯器模式。
- 一個簡單語法需要解釋的場景。
3. 直譯器模式的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”為例:
首先將其拆分為五個元素組成的字串陣列。
迴圈遍歷陣列,首先遍歷到的是100,那麼將其構造成一個NumExpression物件壓入棧。
接著遍歷到了加號運算子,此時將我們剛剛壓入棧的100出棧,作為加號運算子左邊的數字直譯器。
接著,將當前加號運算子下一個角標構造成一個數字直譯器,作為加號運算子右邊的數字直譯器。
將左右兩個數字直譯器作為引數傳入AdditionExpression構造一個加法直譯器,同時壓入棧中。
重複步驟3的操作
重複步驟4的操作。
最後呼叫interpret(),結果相當於exp1.interpret() + exp2.interpret()+exp3.interpret()
5.直譯器模式在Android原始碼中的實現
直譯器模式在Android原始碼中並不常見,這裡就不做具體分析了。
6. 總結:
- 優點:
- 靈活的擴充套件性,當我們想對文法規則進行擴充套件時,只需要增加相應的非終結符號直譯器,並在構建抽象語法樹時,使用到新增的直譯器物件進行具體的解釋即可,非常方便。
- 缺點:
- 過於複雜的語法,構建其抽象語法樹會顯得異常繁瑣,甚至有可能出現需要構建多棵抽象語法樹的情況,因此,對於複雜的文法並不推薦使用直譯器模式。
- 對於每一條文法都可以對應至少一個直譯器,其會生成大量的類,導致後期維護困難。