設計模式(二十一)——直譯器模式(Spring 框架中SpelExpressionParser原始碼分析)

十四lin發表於2021-02-10

1 四則運算問題

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

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

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

3) 最後求出結果:如圖

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

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

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

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

3 直譯器模式基本介紹

基本介紹

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

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

3) 應用場景

  • 應用可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹
  • 一些重複出現的問題可以用一種簡單的語言來表達
  • 一個簡單語法需要解釋的場景

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

4 直譯器模式的原理類圖

 

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

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

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

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

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

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

5 直譯器模式來實現四則

1) 應用例項要求

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

2) 思路分析和圖解(類圖)

3)程式碼實現

 

 

 

package com.lin.interpret;

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

 

package com.lin.interpret;

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) {
        return 0;
    }
}

 

package com.lin.interpret;

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

 

package com.lin.interpret;

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

 

package com.lin.interpret;

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

 

package com.lin.interpret;

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

 

package com.lin.interpret;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

public class Client {

    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 框架應用的原始碼剖析

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

2) 程式碼分析

 

 

 3)說明

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

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

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

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

 

僅供參考,有錯誤還請指出!

有什麼想法,評論區留言,互相指教指教。

覺得不錯的可以點一下右邊的推薦喲!

相關文章