一咏连、什么是解釋器模式
解釋器這個(gè)名詞想必大家都不會(huì)陌生搭幻,比如編譯原理中姆吭,一個(gè)算術(shù)表達(dá)式通過(guò)詞法分析器形成詞法單元已艰,而后這些詞法單元再通過(guò)語(yǔ)法分析器構(gòu)建語(yǔ)法分析樹痊末,最終形成一顆抽象的語(yǔ)法分析樹。諸如此類的例子也有很多哩掺,比如編譯器凿叠、正則表達(dá)式等等。
如果一種特定類型的問題發(fā)生的頻率足夠高疮丛,那么可能就值得將該問題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子幔嫂,這樣就可以構(gòu)建一個(gè)解釋器辆它,該解釋器通過(guò)解釋這些句子來(lái)解決該問題誊薄。
就比如正則表達(dá)式,它就是解釋器模型的一種應(yīng)用锰茉,解釋器為正則表達(dá)式定義了一個(gè)文法呢蔫,如何表示一個(gè)特定的正則表達(dá)式,以及如何解釋這個(gè)正則表達(dá)式飒筑。
解釋器模式(Interpreter)片吊,給定一個(gè)語(yǔ)言,定義它的文法的一種表示协屡,并定義一個(gè)解釋器俏脊,這個(gè)解釋器使用該表示來(lái)解釋語(yǔ)言中的句子。UML結(jié)構(gòu)圖如下:
其中肤晓,Context是環(huán)境角色爷贫,包含解釋器之外的一些全局信息;AbstractExpression為抽象表達(dá)式补憾,聲明一個(gè)抽象的解釋操作漫萄,這個(gè)接口為抽象語(yǔ)法樹中所有的節(jié)點(diǎn)所共享;TerminalExression為終結(jié)符表達(dá)式盈匾,實(shí)現(xiàn)與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作腾务;NonterminalExpression為非終結(jié)符表達(dá)式,為文法中的非終結(jié)符實(shí)現(xiàn)解釋操作削饵,對(duì)文法中每一條規(guī)則R1岩瘦、R2……Rn都需要一個(gè)具體的非終結(jié)符表達(dá)式類未巫。
1. Context環(huán)境角色
public class Context {
private String input;
private String output;
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
}
2. 抽象表達(dá)式
抽象表達(dá)式是生成語(yǔ)法集合(語(yǔ)法樹)的關(guān)鍵,每個(gè)語(yǔ)法集合完成指定語(yǔ)法解析任務(wù),它是通過(guò)遞歸調(diào)用的方式,最終由最小的語(yǔ)法單元進(jìn)行解析完成烫沙。
public abstract class AbstractExpression {
public abstract void Interpret(Context context); }
3. 終結(jié)符表達(dá)式
通常缆瓣,終結(jié)符表達(dá)式比較簡(jiǎn)單,主要處理場(chǎng)景元素和數(shù)據(jù)的轉(zhuǎn)換耀态。
public class TerminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
System.out.println("終端解釋器");
}
}
4. 非終結(jié)符表達(dá)式
每個(gè)非終結(jié)符表達(dá)式都代表了一個(gè)文法規(guī)則,并且每個(gè)文法規(guī)則都只關(guān)心自己周邊的文法規(guī)則的結(jié)果,因此這就產(chǎn)生了每個(gè)非終結(jié)符表達(dá)式調(diào)用自己周邊的非終結(jié)符表達(dá)式饼拍,然后最終、最小的文法規(guī)則就是終結(jié)符表達(dá)式田炭。
public class NonterminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
System.out.println("非終端解釋器");
}
}
5. Client客戶端
其中l(wèi)ist為一個(gè)語(yǔ)法容器师抄,容納一個(gè)具體的表達(dá)式。通常Client是一個(gè)封裝類教硫,封裝的結(jié)果就是傳遞進(jìn)來(lái)一個(gè)規(guī)范語(yǔ)法文件叨吮,解析器分析后產(chǎn)生結(jié)果并返回,避免了調(diào)用者與語(yǔ)法分析器的耦合關(guān)系瞬矩。
public class Client {
public static void main(String[] args) {
Context context = new Context();
List<AbstractExpression> list = new ArrayList<>();
list.add(new TerminalExpression());
list.add(new NonterminalExpression());
list.add(new TerminalExpression());
list.add(new TerminalExpression());
for (AbstractExpression abstractExpression : list) {
abstractExpression.Interpret(context);
}
}
}
運(yùn)行結(jié)果如下:
二茶鉴、解釋器模式的應(yīng)用
1. 何時(shí)使用
- 當(dāng)有一個(gè)語(yǔ)言需要解釋執(zhí)行,并且你可將該語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹時(shí)
2. 方法
- 構(gòu)建語(yǔ)法樹景用,定義終結(jié)符與非終結(jié)符
3. 優(yōu)點(diǎn)
- 可擴(kuò)展性好
4. 缺點(diǎn)
- 解釋器模式會(huì)引起類膨脹
- 解釋器模式采用遞歸調(diào)用方法涵叮,將會(huì)導(dǎo)致調(diào)試非常復(fù)雜
- 使用了大量的循環(huán)和遞歸,效率是一個(gè)不容忽視的問題
5. 使用場(chǎng)景
- 可以將一個(gè)需要解釋執(zhí)行的語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹
- 一些重復(fù)出現(xiàn)的問題可以用一種簡(jiǎn)單的語(yǔ)言來(lái)表達(dá)
- 一個(gè)簡(jiǎn)單語(yǔ)法需要解釋的場(chǎng)景
6. 應(yīng)用實(shí)例
- 編譯器
- 運(yùn)算表達(dá)式計(jì)算伞插、正則表達(dá)式
- 機(jī)器人
7. 注意事項(xiàng)
- 盡量不要在重要的模塊中使用解釋器模式割粮,否則維護(hù)會(huì)是一個(gè)很大的問題
三、解釋器模式的實(shí)現(xiàn)
我們現(xiàn)在通過(guò)解釋器模式來(lái)實(shí)現(xiàn)四則運(yùn)算媚污,如計(jì)算a+b的值舀瓢。UML圖如下:
1. 解析器封裝類
使用Calculator構(gòu)造函數(shù)傳參,并解析封裝耗美。這里根據(jù)棧的“先進(jìn)后出”來(lái)安排運(yùn)算的先后順序(主要用在乘除法京髓,這里只寫了加減法比較簡(jiǎn)單)。以加法為例幽歼,Calculator構(gòu)造函數(shù)接收一個(gè)表達(dá)式朵锣,然后把表達(dá)式轉(zhuǎn)換為char數(shù)組,并判斷運(yùn)算符號(hào)甸私,如果是‘+’則進(jìn)行加法運(yùn)算诚些,把左邊的數(shù)(left變量)和右邊的數(shù)(right變量)加起來(lái)即可。
例如a+b-c這個(gè)表達(dá)式,根據(jù)for循環(huán)诬烹,首先被壓入棧中的是a元素生成的VarExpression對(duì)象砸烦,然后判斷到加號(hào)時(shí),把a(bǔ)元素的對(duì)象從棧中pop出來(lái)绞吁,與右邊的數(shù)組b進(jìn)行相加幢痘,而b是通過(guò)當(dāng)前的數(shù)組游標(biāo)下移一個(gè)單元格得來(lái)的(為了防止該元素被再次遍歷,通過(guò)++i的方式跳過(guò)下一遍歷)家破。減法運(yùn)算同理颜说。
public class Calculator {
//定義表達(dá)式
private Expression expression;
//構(gòu)造函數(shù)傳參,并解析
public Calculator(String expStr) {
//安排運(yùn)算先后順序
Stack<Expression> stack = new Stack<>();
//表達(dá)式拆分為字符數(shù)組
char[] charArray = expStr.toCharArray();
Expression left = null;
Expression right = null;
for(int i=0; i<charArray.length; 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();
}
//計(jì)算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
2. 抽象表達(dá)式類
通過(guò)Map鍵值對(duì)汰聋,使鍵對(duì)應(yīng)公式參數(shù)门粪,如a、b烹困、c等玄妈,值為運(yùn)算時(shí)取得的具體數(shù)值。
public abstract class Expression {
//解析公式和數(shù)值髓梅,key是公式中的參數(shù)拟蜻,value是具體的數(shù)值
public abstract int interpreter(HashMap<String, Integer> var);
}
3. 變量解析器
通過(guò)interpreter()方法從map中取之。
public class VarExpression extends Expression {
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
4. 抽象運(yùn)算符號(hào)解析器
這里枯饿,每個(gè)運(yùn)算符合都只和自己左右兩個(gè)數(shù)字有關(guān)系酝锅,但左右兩個(gè)數(shù)字有可能也是一個(gè)解析的結(jié)果,無(wú)論何種類型鸭你,都是Expression類的實(shí)現(xiàn)類屈张。
public class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpreter(HashMap<String, Integer> var) {
// TODO Auto-generated method stub
return 0;
}
}
5. 加法解析器
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
6. 減法解析器
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
7. Client客戶端
這里就比較簡(jiǎn)單了擒权,通過(guò)getExpStr()方法獲取表達(dá)式袱巨,再通過(guò)getValue()方法獲取值的映射,最后再實(shí)例化Calculator類碳抄,通過(guò)run()方法獲取最終的運(yùn)算結(jié)果愉老。
public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr();
HashMap<String, Integer> var = getValue(expStr);
Calculator calculator = new Calculator(expStr);
System.out.println("運(yùn)算結(jié)果:" + expStr + "=" + calculator.run(var));
}
//獲得表達(dá)式
public static String getExpStr() throws IOException {
System.out.print("請(qǐng)輸入表達(dá)式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//獲得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for(char ch : expStr.toCharArray()) {
if(ch != '+' && ch != '-' ) {
if(! map.containsKey(String.valueOf(ch))) {
System.out.print("請(qǐng)輸入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
運(yùn)算結(jié)果如下: