設計模式學習筆記21 - Java版之解釋器模式

1. 需求

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

  1. 先輸入表達式的形式,比如 a+b+c-d+e, 要求表達式的字母不能重複
  2. 在分別輸入a ,b, c, d, e 的值
  3. 最後求出結果
    在這裏插入圖片描述

2. 傳統方案

  1. 編寫一個方法,接收表達式的形式,然後根據用戶輸入的數值進行解析,得到結果
  2. 問題分析:如果加入新的運算符,比如 * / ( 等等,不利於擴展,另外讓一個方法來解析會造成程序結構混亂,不夠清晰
  3. 解決方案:可以考慮使用解釋器模式, 即: 表達式 -> 解釋器(可以有多種) -> 結果

3. 解釋器模式

3.1 基本介紹

  1. 在編譯原理中,一個算術表達式通過詞法分析器形成詞法單元,而後這些詞法單元再通過語法分析器構建語法分析樹,最終形成一顆抽象的語法分析樹。這裏的詞法分析器和語法分析器都可以看做是解釋器
  2. 解釋器模式(Interpreter Pattern):是指給定一個語言(表達式),定義它的文法的一種表示,並定義一個解釋器,使用該解釋器來解釋語言中的句子(表達式)
  3. 應用場景
    1. 應用可以將一個需要解釋執行的語言中的句子表示爲一個抽象語法樹
    2. 一些重複出現的問題可以用一種簡單的語言來表達
    3. 一個簡單語法需要解釋的場景
  4. 這樣的例子還有,比如編譯器、運算表達式計算、正則表達式、機器人等

3.2 原理類圖

在這裏插入圖片描述

  1. Context: 是環境角色,含有解釋器之外的全局信息
  2. AbstractExpression: 抽象表達式, 聲明一個抽象的解釋操作,這個方法爲抽象語法樹中所有的節點所共享
  3. TerminalExpression: 爲終結符表達式, 實現與文法中的終結符相關的解釋操作
  4. NonTermialExpression: 爲非終結符表達式,爲文法中的非終結符實現解釋操作
  5. 說明: 輸入Context he TerminalExpression 信息通過Client 輸入即可

3.3 實現四則運算

在這裏插入圖片描述

import java.util.HashMap;

/**
 * 抽象類表達式,通過HashMap 鍵值對, 可以獲取到變量的值
 * 
 * @author Administrator
 *
 */
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);
}
import java.util.HashMap;


/**
 * 變量的解釋器
 * @author Administrator
 *
 */
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);
	}
}
import java.util.HashMap;

/**
 * 抽象運算符號解析器 這裏,每個運算符號,都只和自己左右兩個數字有關係,
 * 但左右兩個數字有可能也是一個解析的結果,無論何種類型,都是Expression類的實現類
 * 
 * @author Administrator
 *
 */
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;
	}
}
import java.util.HashMap;

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);
	}
}
import java.util.HashMap;

/**
 * 加法解釋器
 * @author Administrator
 *
 */
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);
	}
}
import java.util.HashMap;
import java.util.Stack;

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);
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

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;
	}
}

3.4 細節

  1. 當有一個語言需要解釋執行,可將該語言中的句子表示爲一個抽象語法樹,就可以考慮使用解釋器模式,讓程序具有良好的擴展性
  2. 應用場景:編譯器、運算表達式計算、正則表達式、機器人等
  3. 使用解釋器可能帶來的問題:解釋器模式會引起類膨脹、解釋器模式採用遞歸調用方法,將會導致調試非常複雜、效率可能降低
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章