[設計模式]解釋器模式

感謝《Android源碼設計模式解析與實戰》 何紅輝 關愛民 著

解釋器模式(Interpreter Pattern)是一種用得比較少的行爲型模式,其提供了一種解釋語言的語法或表達式的方式,該模式定義了一個表達式接口,通過該接口解釋一個特定的上下文。解釋器模式在實際運用上相對來說要少很多,因爲我們很少會自己去構造一個語言的方法。


定義

給定一個語言,定義它的方法的一種表示,並定義一個解釋器,該解釋器使用該表示來解釋語言中的句子。


使用場景

解釋器模式的使用場景其實相廣泛,總的概括大致有兩種:

1.如果某個簡單的語言需要解釋執行而且可以將該語言中的語句表示爲一個抽象語法樹時可以考慮使用解釋器模式。

2.在某些特定的領域出現不斷重複的問題時,可以將該領域的問題轉化爲一種語法規則下的語句,無用中來解釋該語句。


UML類圖

AbstractExpression:抽象表達式,聲明一個抽象的解釋操作父類,並定義一個抽象的解釋方法,其具體的實現在各個具體的子類解釋器中完成。

TerminalExpression:終結符表達式,實現文法中與終結符有關的解釋操作。文法中每一個終結符都有一個具體的終結表達式與之對應。

NonterminalExpression:非終結符表達式,實現文法中與非終結符有關的解釋操作。

Context:上下文環境類,包含解釋器之外的全局信息。

Client :客戶類,解析表達式,構建抽象語法樹,執行具體的解釋操作等。


示例

一個比較常見的場景是對算術表達式的解釋,如“m + n + p",如果我們使用解釋器模式對該表達式,那麼代表數字的m、n、p我們就可以看成是終結符號,而 + 算術運算符號則可當作非終結符號

/**
 * 抽象的算術運算解釋器  爲所有解釋器共性的提取
 * @author Administrator
 *
 */
public abstract class ArithmeticExpression {
	
	/**
	 * 抽象的解析方法
	 * 具體的解析邏輯由具體的子類實現
	 * @return
	 */
	public abstract int interpret();
}

/**
 * 數字解釋器
 * @author Administrator
 *
 */
public class NumExpression extends ArithmeticExpression{
	private int num;
	
	public NumExpression(int num) {
		this.num = num;
	}
	
	@Override
	public int interpret() {
		return num;
	}

}


OperatorExpression依然是不個抽象類,其聲明兩個ArithmeticExpression類型的成員變量存儲運算符號兩邊的數字解釋器

/**
 * 運算符號抽象解釋器 爲所有運算符號解釋器共性的提取
 * @author Administrator
 *
 */
public abstract class OperatorExpression extends ArithmeticExpression{
	//聲明兩個成員變量存儲運算符號兩邊的數字解釋器
	protected ArithmeticExpression exp1,exp2;
	
	public OperatorExpression(ArithmeticExpression exp1, ArithmeticExpression exp2) {
		this.exp1 = exp1;
		this.exp2 = exp2;
	}
}

/**
 * 加法運算抽象解釋器
 * @author Administrator
 *
 */
public class AdditionExpression extends OperatorExpression{

	public AdditionExpression(ArithmeticExpression exp1, ArithmeticExpression exp2) {
		super(exp1, exp2);
	}

	@Override
	public int interpret() {
		return exp1.interpret() + exp2.interpret();
	}

}
除此之外,我們創建一個Calculator類來處理一些相關的業務。

/**
 * 處理與解釋器相關的一些業務
 * @author Administrator
 *
 */
public class Calculator {
	//聲明一個Stack棧存儲並操作所有相關的解釋器
	private Stack<ArithmeticExpression> mExpStack = new Stack<ArithmeticExpression>();
	
	public Calculator(String expression) {
		//聲明兩個ArithmeticExpression類型的臨時變量,存儲運算符左右兩邊的數字解釋器
		ArithmeticExpression exp1,exp2;
		
		//根據空格分割表達式字符串
		String[] elements = expression.split(" ");
		
		/**
		 * 循環遍歷表達式元素數組
		 */
		for(int i = 0; i < elements.length; i++){
			switch(elements[i].charAt(0)){
			case '+':
				exp1 = mExpStack.pop(); //如果是加號則將棧中的解釋器彈出作爲運算符號左邊的解釋器
				exp2 = new NumExpression(Integer.valueOf(elements[i]));
				mExpStack.push(new AdditionExpression(exp1, exp2));
				break;
			case '-' :
				mExpStack.push(new NumExpression(Integer.valueOf(elements[i])));  //若是數字,直接構造數字解釋器並壓入棧
				break;
			}
		}
	}
	
	/**
	 * 計算結果
	 * @return
	 */
	public int calculate(){
		return mExpStack.pop().interpret();
	}
}
Calculator類的邏輯很好理解,首先約定每個元素之間必須使用空格隔開,將其拆分爲字符串數組,然後循環遍歷,首先遍歷到的元素是數字,那麼將其作爲參數構造一個NumExpression對象壓入棧,其次是運算符,如果是加號我們則將剛纔壓入棧的數字作爲參數構造一個NumExpression對象拋出作爲加號左路邊的數字解釋器,右邊的數字解釋器我們只需要將當前數組下標+1獲取到數組元素中當前加號右邊的數字,將其作爲參數構造一個NumExpression對象即可,這個過程其實就是在構建語法樹,只不過我們將其單獨封裝在了一個類裏。

/**
 * 處理與解釋器相關的一些業務
 * @author Administrator
 *
 */
public class Calculator {
	//聲明一個Stack棧存儲並操作所有相關的解釋器
	private Stack<ArithmeticExpression> mExpStack = new Stack<ArithmeticExpression>();
	
	public Calculator(String expression) {
		//聲明兩個ArithmeticExpression類型的臨時變量,存儲運算符左右兩邊的數字解釋器
		ArithmeticExpression exp1,exp2;
		
		//根據空格分割表達式字符串
		String[] elements = expression.split(" ");
		
		if(elements == null){
			return;
		}
		/**
		 * 循環遍歷表達式元素數組
		 */
		for(int i = 0; i < elements.length; i++){
			switch(elements[i].charAt(0)){
			case '+':
				exp1 = mExpStack.pop(); //如果是加號則將棧中的解釋器彈出作爲運算符號左邊的解釋器
				exp2 = new NumExpression(Integer.valueOf(elements[++i]));
				mExpStack.push(new AdditionExpression(exp1, exp2));
				break;
			default :
				mExpStack.push(new NumExpression(Integer.valueOf(elements[i])));  //若是數字,直接構造數字解釋器並壓入棧
				break;
			}
		}
	}
	
	/**
	 * 計算結果
	 * @return
	 */
	public int calculate(){
		return mExpStack.pop().interpret();
	}
}
如果現在又想引入減法運算,定義一個減法解釋器即可:

public class SubtractionExpression extends OperatorExpression{

	public SubtractionExpression(ArithmeticExpression exp1, ArithmeticExpression exp2) {
		super(exp1, exp2);
	}

	@Override
	public int interpret() {
		return exp1.interpret() - exp2.interpret();
	}

}
還需要修改下Calculator類構建語法樹的邏輯

case '-':
				exp1 = mExpStack.pop();
				exp2 = new NumExpression(Integer.valueOf(elements[++i]));
				mExpStack.push(new SubtractionExpression(exp1, exp2));
				break;
public class Client {
	public static void main(String[] args) {
		Calculator c = new Calculator("113 + 20 + 9 + 1033 + 6 - 11");
		System.out.println(c.calculate());
	}
	
	//運行結果:
	//1170
}

可以看到解釋器模式的一個優點:就是靈活性強,如果想實現更多的運算法則,只需創建對應的運算解釋器即可,但是混合運算要比簡單的加減複雜得多,還要考慮不同符號的運算優先級。

具體的文法規則與解釋器之間其實是由對應關係的,大多數情況下兩者之間是一一對應的關係,即一條文法對應一個解釋器,我們也可以爲一條文法創建多個不同的解釋器,但是反過來就不行,因爲一個解釋器不能即解釋加法運算又解釋減法,否則就違背瞭解釋器的定義。

將一條具體的文法通過一個解釋器解釋,把複雜的文法規則分離爲簡單的功能進行解釋,最後將其組合成一棵抽象語法樹解釋執行。至此我們可以看到解釋器的本原理和本質:將複雜的問題簡單化、模塊化、分離實現、解釋執行。

總結

優點

解釋器模式的優點是其靈活的擴展性,當我們想對文法規則進行擴展延伸時,只需要增加相應的非終結解釋器,並在構建抽象語法樹時,使用到新增的解釋器對象進行具體的解釋即可。

缺點

對於每一條文法都可以對應至少一個解釋器,其會生成大量的類,導致後期維護困難;同時,對於過於複雜的文法,構建其抽象語法樹會顯得異常繁瑣,甚至有可能會出現需要構建多棵抽象語法樹的情況,因此,對於複雜的方法並不推薦使用解釋器模式。






發佈了49 篇原創文章 · 獲贊 18 · 訪問量 9萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章