1. 程式人生 > 其它 >21 直譯器模式

21 直譯器模式

直譯器模式

四則運算問題

通過直譯器模式來實現四則運算,如計算 a+b-c 的值,具體要求

  1. 先輸入表示式的形式,比如 a+b+c-d+e, 要求表示式的字母不能重複

  2. 在分別輸入 a ,b, c, d, e 的值

傳統方案解決四則運算問題分析

  1. 編寫一個方法,接收表示式的形式,然後根據使用者輸入的數值進行解析,得到結果

  2. 問題分析:如果加入新的運算子,比如 * / ( 等等,不利於擴充套件,另外讓一個方法來解析會造成程式結構混亂, 不夠清晰.

  3. 解決方案:可以考慮使用直譯器模式, 即: 表示式 -> 直譯器(可以有多種) -> 結果

直譯器模式基本介紹

基本介紹

  1. 在編譯原理中,一個算術表示式通過詞法分析器形成詞法單元,而後這些詞法單元再通過語法分析器構建語法分析樹,最終形成一顆抽象的語法分析樹。這裡的詞法分析器和語法分析器都可以看做是直譯器

  2. 直譯器模式(Interpreter Pattern):是指給定一個語言(表示式),定義它的文法的一種表示,並定義一個直譯器, 使用該直譯器來解釋語言中的句子(表示式)

  3. 應用場景

-應用可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹

-一些重複出現的問題可以用一種簡單的語言來表達

一個簡單語法需要解釋的場景

  1. 這樣的例子還有,比如編譯器、運算表示式計算、正則表示式、機器人等

直譯器模式的原理類圖

對原理類圖的說明-即(直譯器模式的角色及職責)

  1. Context: 是環境角色,含有直譯器之外的全域性資訊.

  2. AbstractExpression: 抽象表示式, 宣告一個抽象的解釋操作,這個方法為抽象語法樹中所有的節點所共享

  3. TerminalExpression: 為終結符表示式, 實現與文法中的終結符相關的解釋操作

  4. NonTermialExpression: 為非終結符表示式,為文法中的非終結符實現解釋操作.

  5. 說明: 輸入 Context he TerminalExpression 資訊通過 Client 輸入即可

直譯器模式來實現四則

  1. 應用例項要求

通過直譯器模式來實現四則運算, 如計算 a+b-c 的值

  1. 思路分析和圖解(類圖)

程式碼實現

/**
 * 抽象類表示式,通過HashMap 鍵值對, 可以獲取到變數的值
*/
public abstract class Expression {
	// a + b - c
	// 解釋公式和數值, key 就是公式(表示式) 引數[a,b,c], value就是就是具體值
	// HashMap {a=10, b=20}
	public abstract int interpreter(HashMap<String, Integer> var);
}

/**
 * 變數的直譯器
 */
public class VarExpression extends Expression {

	private String key; // key=a,key=b,key=c

	public VarExpression(String key) {
		this.key = key;
	}

	// var 就是{a=10, b=20}
	// interpreter 根據 變數名稱,返回對應值
	@Override
	public int interpreter(HashMap<String, Integer> var) {
		return var.get(this.key);
	}
}

/**
 * 抽象運算子號解析器 這裡,每個運算子號,都只和自己左右兩個數字有關係,
 * 但左右兩個數字有可能也是一個解析的結果,無論何種型別,都是Expression類的實現類
 */
public class SymbolExpression extends Expression {

	protected Expression left;
	protected Expression right;

	public SymbolExpression(Expression left, Expression right) {
		this.left = left;
		this.right = right;
	}

	//因為 SymbolExpression 是讓其子類來實現,因此 interpreter 是一個預設實現
	@Override
	public int interpreter(HashMap<String, Integer> var) {
		// TODO Auto-generated method stub
		return 0;
	}
}

public class SubExpression extends SymbolExpression {

	public SubExpression(Expression left, Expression right) {
		super(left, right);
	}

	//求出left 和 right 表示式相減後的結果
	public int interpreter(HashMap<String, Integer> var) {
		return super.left.interpreter(var) - super.right.interpreter(var);
	}
}

/**
 * 加法直譯器
 */
public class AddExpression extends SymbolExpression  {

	public AddExpression(Expression left, Expression right) {
		super(left, right);
	}

	//處理相加
	//var 仍然是 {a=10,b=20}..
	//一會我們debug 原始碼,就ok
	public int interpreter(HashMap<String, Integer> var) {
		//super.left.interpreter(var) : 返回 left 表示式對應的值 a = 10
		//super.right.interpreter(var): 返回right 表示式對應值 b = 20
		return super.left.interpreter(var) + super.right.interpreter(var);
	}
}

public class Calculator {

	// 定義表示式
	private Expression expression;

	// 建構函式傳參,並解析
	public Calculator(String expStr) { // expStr = a+b
		// 安排運算先後順序
		Stack<Expression> stack = new Stack<>();
		// 表示式拆分成字元陣列 
		char[] charArray = expStr.toCharArray();// [a, +, b]

		Expression left = null;
		Expression right = null;
		//遍歷我們的字元陣列, 即遍歷  [a, +, b]
		//針對不同的情況,做處理
		for (int i = 0; i < charArray.length; i++) {
			switch (charArray[i]) {
			case '+': //
				left = stack.pop();// 從stack取出left => "a"
				right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表示式 "b"
				stack.push(new AddExpression(left, right));// 然後根據得到left 和 right 構建 AddExpresson加入stack
				break;
			case '-': // 
				left = stack.pop();
				right = new VarExpression(String.valueOf(charArray[++i]));
				stack.push(new SubExpression(left, right));
				break;
			default: 
				//如果是一個 Var 就建立要給 VarExpression 物件,並push到 stack
				stack.push(new VarExpression(String.valueOf(charArray[i])));
				break;
			}
		}
		//當遍歷完整個 charArray 陣列後,stack 就得到最後Expression
		this.expression = stack.pop();
	}

	public int run(HashMap<String, Integer> var) {
		//最後將表示式a+b和 var = {a=10,b=20}
		//然後傳遞給expression的interpreter進行解釋執行
		return this.expression.interpreter(var);
	}
}

public class ClientTest {

	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		String expStr = getExpStr(); // a+b
		HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
		Calculator calculator = new Calculator(expStr);
		System.out.println("運算結果:" + expStr + "=" + calculator.run(var));
	}

	// 獲得表示式
	public static String getExpStr() throws IOException {
		System.out.print("請輸入表示式:");
		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("請輸入" + String.valueOf(ch) + "的值:");
					String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
					map.put(String.valueOf(ch), Integer.valueOf(in));
				}
			}
		}

		return map;
	}
}

直譯器模式在Spring 框架應用的原始碼剖析

  1. Spring 框架中 SpelExpressionParser 就使用到直譯器模式

直譯器模式的注意事項和細節

  1. 當有一個語言需要解釋執行,可將該語言中的句子表示為一個抽象語法樹,就可以考慮使用直譯器模式,讓程式具有良好的擴充套件性

  2. 應用場景:編譯器、運算表示式計算、正則表示式、機器人等

  3. 使用直譯器可能帶來的問題:直譯器模式會引起類膨脹、直譯器模式採用遞迴呼叫方法,將會導致除錯非常複雜、效率可能降低.