「補課」進行時:設計模式(20)——解釋器模式

1. 前文匯總
2. 解釋器模式
解釋器模式這個模式和前面的訪問者模式比較像,當然,我說的比較像是難的比較像,以及使用率是真的比較低,基本上沒有使用的場景,訪問者模式還有點使用場景,解釋器模式,我們又不寫解釋器,這玩意 JVM 都幫我們實現(xiàn)掉了,哪用我們自己實現(xiàn)。
常見的解釋器有 JVM 為我們提供的 Java 語言的解釋器,還有我們經(jīng)常使用的 MySQL ,也有內(nèi)置的 SQL 解釋器。
不過沒用是沒用,對應的模式我們還是可以學習一下。
2.1 定義
解釋器模式(Interpreter Pattern) 是一種按照規(guī)定語法進行解析的方案,其定義如下:
Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(給定一門語言, 定義它的文法的一種表示, 并定義一個解釋器, 該解釋器使用該表示來解釋語言中的句子。)
2.2 通用類圖

AbstractExpression 抽象解釋器:具體的解釋任務由各個實現(xiàn)類完成, 具體的解釋器分別由 TerminalExpression 和 NonterminalExpression 完成。 TerminalExpression 終結(jié)符表達式:實現(xiàn)與文法中的元素相關(guān)聯(lián)的解釋操作, 通常一個解釋器模式中只有一個終結(jié)符表達式, 但有多個實例, 對應不同的終結(jié)符。 NonterminalExpression 非終結(jié)符表達式:文法中的每條規(guī)則對應于一個非終結(jié)表達式, 具體到我們的例子就是加減法規(guī)則分別對應到 AddExpression 和 SubExpression 兩個類。非終結(jié)符表達式根據(jù)邏輯的復雜程度而增加, 原則上每個文法規(guī)則都對應一個非終結(jié)符表達式。 Context 環(huán)境角色
2.3 通用代碼
抽象表達式
public?abstract?class?Expression?{
????abstract?Object?interpreter(Context?ctx);
}
抽象表達式比較簡單,通常只有一個方法,但是它是生成語法集合(也叫做語法樹) 的關(guān)鍵, 每個語法集合完成指定語法解析任務, 它是通過遞歸調(diào)用的方式, 最終由最小的語法單元進行解析完成。
終結(jié)符表達式
public?class?TerminalExpression?extends?Expression?{
????//?通常終結(jié)符表達式只有一個,?但是有多個對象
????public?Object?interpreter(Context?context)?{
????????return?null;
????}
}
終結(jié)符表達式比較簡單, 主要是處理場景元素和數(shù)據(jù)的轉(zhuǎn)換。
非終結(jié)符表達式
public?class?NonterminalExpression?extends?Expression?{
????//?每個非終結(jié)符表達式都會對其他表達式產(chǎn)生依賴
????public?NonterminalExpression(Expression?...expressions)?{
????}
????@Override
????Object?interpreter(Context?ctx)?{
????????//?進行文法處理
????????return?null;
????}
}
每個非終結(jié)符表達式都代表了一個文法規(guī)則, 并且每個文法規(guī)則都只關(guān)心自己周邊的文法規(guī)則的結(jié)果(注意是結(jié)果) , 因此這就產(chǎn)生了每個非終結(jié)符表達式調(diào)用自己周邊的非終結(jié)符表達式, 然后最終、 最小的文法規(guī)則就是終結(jié)符表達式, 終結(jié)符表達式的概念就是如此,不能夠再參與比自己更小的文法運算了。
客戶端
public?class?Client?{
????public?static?void?main(String[]?args)?{
????????Context?ctx?=?new?Context();
????????Stack?stack?=?null;
????????for(int?i?=?0;?i?????????????//?進行語法判斷,?并產(chǎn)生遞歸調(diào)用
????????}
????????//?產(chǎn)生一個完整的語法樹,?由各個具體的語法分析進行解析
????????Expression?exp?=?stack.pop();
????????//具體元素進入場景
????????exp.interpreter(ctx);
????}
}
2.4 優(yōu)點
解釋器是一個簡單語法分析工具, 它最顯著的優(yōu)點就是擴展性, 修改語法規(guī)則只要修改相應的非終結(jié)符表達式就可以了, 若擴展語法, 則只要增加非終結(jié)符類就可以了。
2.5 缺點
解釋器模式會引起類膨脹。 解釋器模式采用遞歸調(diào)用方法,將會導致調(diào)試非常復雜。 使用了大量的循環(huán)和遞歸,效率是一個不容忽視的問題。
3. 四則運算
簡單使用解釋器模式實現(xiàn)一下加減法運算。
首先定義一個計算類,用作解析器封裝:
public?class?Calculator?{
????private?Expression?expression;
????//?構(gòu)造函數(shù),傳參并解析
????public?Calculator(String?expStr)?{
????????//?安排運算先后順序
????????Stack?stack?=?new?Stack<>();
????????//?表達式拆分為字符數(shù)組
????????char[]?charArray?=?expStr.toCharArray();
????????Expression?left?=?null;
????????Expression?right?=?null;
????????for(int?i=0;?i????????????switch?(charArray[i])?{
????????????????case?'+':
????????????????????left?=?stack.pop();
????????????????????right?=?new?VarExpression(String.valueOf(charArray[++i]));
????????????????????stack.push(new?AddExpression(left,?right));
????????????????????break;
????????????????case?'-':
????????????????????left?=?stack.pop();
????????????????????right?=?new?VarExpression(String.valueOf(charArray[++i]));
????????????????????stack.push(new?SubExpression(left,?right));
????????????????????break;
????????????????default?:
????????????????????stack.push(new?VarExpression(String.valueOf(charArray[i])));
????????????????????break;
????????????}
????????}
????????this.expression?=?stack.pop();
????}
????public?int?run(HashMap?var) ?{
????????return?this.expression.interpreter(var);
????}
}
接下來是一個抽象表達式:
public?abstract?class?Expression?{
????abstract?int?interpreter(HashMap?var) ;
}
下面是一個變量解析器:
public?class?VarExpression?extends?Expression?{
????private?String?key;
????public?VarExpression(String?key)?{
????????this.key?=?key;
????}
????@Override
????int?interpreter(HashMap?var) ?{
????????return?var.get(this.key);
????}
}
變量解析器的主要作用是從 map 中將數(shù)據(jù)一個一個取出來。
接下來是一個抽象運算符號解析器:
public?class?SymbolExpression?extends?Expression?{
????protected?Expression?left;
????protected?Expression?right;
????public?SymbolExpression(Expression?left,?Expression?right)?{
????????this.left?=?left;
????????this.right?=?right;
????}
????@Override
????int?interpreter(HashMap?var) ?{
????????return?0;
????}
}
因為是運算符號,而每個運算符號都只和自己左右兩個數(shù)字有關(guān)系,左右兩個數(shù)字有可能也是一個解析的結(jié)果,無論何種類型,都是 Expression 類的實現(xiàn)類。
接下來是兩個具體的運算符號解析器,一個加號解析器和一個減號解析器:
public?class?AddExpression?extends?SymbolExpression?{
????public?AddExpression(Expression?left,?Expression?right)?{
????????super(left,?right);
????}
????@Override
????int?interpreter(HashMap?var) ?{
????????return?super.left.interpreter(var)?+?super.right.interpreter(var);
????}
}
public?class?SubExpression?extends?SymbolExpression?{
????public?SubExpression(Expression?left,?Expression?right)?{
????????super(left,?right);
????}
????@Override
????int?interpreter(HashMap?var) ?{
????????return?super.left.interpreter(var)?-?super.right.interpreter(var);
????}
}
最后是我們的客戶端類:
public?class?Client?{
????public?static?void?main(String[]?args)?throws?IOException?{
????????String?expStr?=?getExpStr();
????????HashMap?var?=?getValue(expStr);
????????Calculator?calculator?=?new?Calculator(expStr);
????????System.out.println("運算結(jié)果:"?+?expStr?+?"="?+?calculator.run(var));
????}
????public?static?String?getExpStr()?throws?IOException?{
????????System.out.print("請輸入表達式:");
????????return?(new?BufferedReader(new?InputStreamReader(System.in))).readLine();
????}
????public?static?HashMap?getValue(String?expStr)?throws?IOException? {
????????HashMap?map?=?new?HashMap<>();
????????for(char?ch?:?expStr.toCharArray())?{
????????????if(ch?!=?'+'?&&?ch?!=?'-'?)?{
????????????????if(!?map.containsKey(String.valueOf(ch)))?{
????????????????????System.out.print("請輸入"?+?String.valueOf(ch)?+?"的值:");
????????????????????String?in?=?(new?BufferedReader(new?InputStreamReader(System.in))).readLine();
????????????????????map.put(String.valueOf(ch),?Integer.valueOf(in));
????????????????}
????????????}
????????}
????????return?map;
????}
}
執(zhí)行結(jié)果如下:
請輸入表達式:a+b-c
請輸入a的值:10
請輸入b的值:20
請輸入c的值:13
運算結(jié)果:a+b-c=17
解釋器模式在實際的系統(tǒng)開發(fā)中使用得非常少, 因為它會引起效率、 性能以及維護等問題, 一般在大中型的框架型項目能夠找到它的身影, 如一些數(shù)據(jù)分析工具、 報表設計工具、科學計算工具等。
如果遇到確定要使用解析器的場景,可以考慮一下 Expression4J 、 MESP(Math Expression String Parser) 、 Jep 等開源的解析工具包,功能都異常強大,而且非常容易使用,效率也還不錯, 實現(xiàn)大多數(shù)的數(shù)學運算沒有問題,完全自己沒有必要從頭開始編寫解釋器。

