直譯器設計模式知識概括

GeorgeLin~發表於2020-10-13

直譯器設計模式簡介

四則運算問題:

  • 通過直譯器模式來實現四則運算,如計算a+b-c的值,具體要求
    ①先輸入表示式的形式,比如 a+b+c-d+e,要求表示式的字母不能重複
    ②在分別輸入 a, b, c, d, e 的值
    ③最後求出結果:如圖在這裡插入圖片描述

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

  • 編寫一個方法,接收表示式的形式,然後根據使用者輸入的數值進行解析,得到結果
  • 問題分析:如果加入新的運算子,比如 ‘*’、’/’ ( 等等,不利於擴充套件, 另外讓一個方法來解析會造成程式結構混亂,不夠清晰
  • 解決方案:可以考慮使用直譯器模式, 即: 表示式 --> 直譯器(可以有多種) --> 結果

直譯器模式基本介紹:

  • 在編譯原理中,一個算術表示式通過詞法分析器形成詞法單元,而後這些詞法單元再通過語法分析器構建語法分析樹,最終形成一顆抽象的語法分析樹。這裡的詞法分析器和語法分析器都可以看做是直譯器
  • 直譯器模式(Interpreter Pattern):是指給定一個語言(表示式),我們程式設計人員需要定義語句的文法的一種表示,並定義一個直譯器, 使用該直譯器來解釋語言中的句子(表示式)

應用場景:

  • 應用可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹
  • 一些重複出現的問題可以用一種簡單的語言來表達
  • 一個簡單語法需要解釋的場景
  • 這樣的例子還有,比如編譯器、 運算表示式計算、正規表示式、 機器人等

直譯器模式的原理類圖:

  • Context:是環境角色,含有直譯器之外的全域性資訊
  • AbstractExpression:抽象表示式, 宣告一個抽象的解釋操作,這個方法為抽象語法樹中所有的節點所共享
  • TerminalExpression:為終結符表示式,實現與文法中的終結符相關的解釋操作
  • NonTermialExpression:為非終結符表示式,為文法中的非終結符實現解釋操作
  • 說明: 輸入 Context 和 TerminalExpression 資訊通過 Client 輸入即可在這裡插入圖片描述

直譯器模式來實現四則運算

簡介:

  • 應用例項要求:通過直譯器模式來實現四則運算,如計算a+b-c的值
  • 類圖:在這裡插入圖片描述
  • 程式碼實現:

Expression:表示式直譯器的抽象父類

/**
 * 抽象類表示式,通過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);
}

VarExpression:變數的直譯器,重寫了 interpreter() 方法,該方法通過變數的名稱獲取變數的值

/**
 * 變數的直譯器
 * 
 * @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);
	}
}

SymbolExpression:抽象運算子的直譯器,由於運算子有多中型別,所以在 interpreter() 無法完成具體的運算操作,具體操作交由子類實現

/**
 * 抽象運算子號解析器 這裡,每個運算子號,都只和自己左右兩個數字有關係,
 * 但左右兩個數字有可能也是一個解析的結果,無論何種型別,都是Expression類的實現類
 * 
 * @author Administrator
 *
 */
public abstract 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) {
		return 0;
	}
}

AddExpression:加法運算子的直譯器,遞迴呼叫:表示式的值 = 左邊表示式的值 + 右邊表示式的值

/**
 * 加法直譯器
 * 
 * @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) {
		// left.interpreter(var) : 返回 left 表示式對應的值 a = 10
		// right.interpreter(var): 返回right 表示式對應值 b = 20
		return left.interpreter(var) + right.interpreter(var);
	}
}

SubExpression:減法運算子的直譯器,遞迴呼叫:表示式的值 = 左邊表示式的值 + 右邊表示式的值

/**
 * 減法直譯器
 * 
 * @author Administrator
 *
 */
public class SubExpression extends SymbolExpression {

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

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

Calculator:計算器類,通過 Expression 的實現類完成具體的解釋運算

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

ClientTest:測試程式碼

public class ClientTest {

	public static void main(String[] args) throws IOException {
		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 SpelExpressionParser

測試程式碼:

public class Interpreter {

	public static void main(String[] args) {
		// 建立一個 Parser 物件
		ExpressionParser parser = new SpelExpressionParser();

		// 通過 Parser 物件 獲取到一個Expression物件
		// 會根據不同的 Parser 物件 ,返回不同的 Expression物件
		Expression expression = parser.parseExpression("10 * (2 + 1) * 1 + 66"); // 96
		int result = (Integer) expression.getValue();
		System.out.println(result);
	}

}

原始碼追蹤:

  • ExpressionParser 是個介面,定義了獲取 Express 表示式物件的行為規範,其繼承關係如下

在這裡插入圖片描述

public interface ExpressionParser {

	Expression parseExpression(String expressionString) throws ParseException;

	Expression parseExpression(String expressionString, ParserContext context) throws ParseException;

}
  • TemplateAwareExpressionParser 實現了 ExpressionParser 介面,其中有個重要的方法為parseExpression(),該方法根據表示式的值,獲取 Express 物件;在TemplateAwareExpressionParser 中實現了獲取 CompositeStringExpression表示式物件的方法
public abstract class TemplateAwareExpressionParser implements ExpressionParser {
    
    // ...
    
    @Override
	public Expression parseExpression(String expressionString) throws ParseException {
		return parseExpression(expressionString, NON_TEMPLATE_PARSER_CONTEXT);
	}

	@Override
	public Expression parseExpression(String expressionString, ParserContext context)
			throws ParseException {
		if (context == null) {
			context = NON_TEMPLATE_PARSER_CONTEXT;
		}

		if (context.isTemplate()) {
			return parseTemplate(expressionString, context); // 建立 CompositeStringExpression 物件
		}
		else {
			return doParseExpression(expressionString, context); // 建立 SpelExpression 物件
		}
	}
    
    
    // ...
    
    private Expression parseTemplate(String expressionString, ParserContext context)
        	throws ParseException {
		if (expressionString.length() == 0) {
			return new LiteralExpression("");
		}
		Expression[] expressions = parseExpressions(expressionString, context);
		if (expressions.length == 1) {
			return expressions[0];
		}
		else {
			return new CompositeStringExpression(expressionString, expressions);
		}
	}
    
    protected abstract Expression doParseExpression(String expressionString,
		ParserContext context) throws ParseException;

    // ...
  • SpelExpressionParser 類繼承自 TemplateAwareExpressionParser 類,實現了
    doParseExpression() 抽象方法
public class SpelExpressionParser extends TemplateAwareExpressionParser {
    
    // ...
    
    @Override
	protected SpelExpression doParseExpression(String expressionString, ParserContext context) throws ParseException {
		return new InternalSpelExpressionParser(this.configuration).doParseExpression(expressionString, context);
	}
    
    // ...
  • Expression 是個介面,定義了表示式直譯器的業務規範,裡面有超多的 getValue() 方法,用於獲取表示式的值,其繼承關係如下在這裡插入圖片描述
public interface Expression {

	Object getValue() throws EvaluationException;

	Object getValue(Object rootObject) throws EvaluationException;

	<T> T getValue(Class<T> desiredResultType) throws EvaluationException;

	<T> T getValue(Object rootObject, Class<T> desiredResultType) throws EvaluationException;
    
    // ...
  • SpelExpression 實現了 Expression 介面,重寫了超多的 getValue() 方法,主要是通過呼叫SpelNodeImpl 類中的方法來實現 Expresssion 中的 getValue() 方法
public class SpelExpression implements Expression {

	private final String expression;

	private final SpelNodeImpl ast;

	private final SpelParserConfiguration configuration;

	// the default context is used if no override is supplied by the user
	private EvaluationContext defaultContext;


	/**
	 * Construct an expression, only used by the parser.
	 */
	public SpelExpression(String expression, SpelNodeImpl ast, SpelParserConfiguration configuration) {
		this.expression = expression;
		this.ast = ast;
		this.configuration = configuration;
	}


	// implementing Expression

	@Override
	public Object getValue() throws EvaluationException {
		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
		return this.ast.getValue(expressionState);
	}

	@Override
	public Object getValue(Object rootObject) throws EvaluationException {
		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
		return this.ast.getValue(expressionState);
	}

	@Override
	public <T> T getValue(Class<T> expectedResultType) throws EvaluationException {
		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), this.configuration);
		TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
		return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
	}

	@Override
	public <T> T getValue(Object rootObject, Class<T> expectedResultType) throws EvaluationException {
		ExpressionState expressionState = new ExpressionState(getEvaluationContext(), toTypedValue(rootObject), this.configuration);
		TypedValue typedResultValue = this.ast.getTypedValue(expressionState);
		return ExpressionUtils.convertTypedValue(expressionState.getEvaluationContext(), typedResultValue, expectedResultType);
	}

	@Override
	public Object getValue(EvaluationContext context) throws EvaluationException {
		Assert.notNull(context, "The EvaluationContext is required");
		return this.ast.getValue(new ExpressionState(context, this.configuration));
	}
    
    // ...

總結:

  • Expression 介面有不同的實現類,比如 SpelExpression,使用的時候,根據我們建立的 ExpressionParser物件不同,就返回不同的 Expression 物件

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

  • 當有一個語言需要解釋執行, 可將該語言中的句子表示為一個抽象語法樹, 就可以考慮使用直譯器模式, 讓程式具有良好的擴充套件性
  • 應用場景: 編譯器、 運算表示式計算、 正規表示式、 機器人等
  • 使用直譯器可能帶來的問題: 直譯器模式會引起類膨脹、 直譯器模式採用遞迴呼叫方法, 將會導致除錯非常複雜、 效率可能降低

相關文章