1. 程式人生 > >「補課」進行時:設計模式(20)——直譯器模式

「補課」進行時:設計模式(20)——直譯器模式

![](https://cdn.geekdigging.com/DesignPatterns/java_design_pattern.jpg) ## 1. 前文彙總 [「補課」進行時:設計模式系列](https://www.geekdigging.com/category/%e8%ae%be%e8%ae%a1%e6%a8%a1%e5%bc%8f/) ## 2. 直譯器模式 直譯器模式這個模式和前面的訪問者模式比較像,當然,我說的比較像是難的比較像,以及使用率是真的比較低,基本上沒有使用的場景,訪問者模式還有點使用場景,直譯器模式,我們又不寫直譯器,這玩意 JVM 都幫我們實現掉了,哪用我們自己實現。 常見的直譯器有 JVM 為我們提供的 Java 語言的直譯器,還有我們經常使用的 MySQL ,也有內建的 SQL 直譯器。 不過沒用是沒用,對應的模式我們還是可以學習一下。 ### 2.1 定義 直譯器模式(Interpreter Pattern) 是一種按照規定語法進行解析的方案,其定義如下: Given a language, define a representation for its grammar along with an interpreter that uses the representation to interpret sentences in the language.(給定一門語言, 定義它的文法的一種表示, 並定義一個直譯器, 該直譯器使用該表示來解釋語言中的句子。 ) ### 2.2 通用類圖 ![](https://cdn.geekdigging.com/DesignPatterns/20/Interpreter_UML.png) - AbstractExpression 抽象直譯器:具體的解釋任務由各個實現類完成, 具體的直譯器分別由 TerminalExpression 和 NonterminalExpression 完成。 - TerminalExpression 終結符表示式:實現與文法中的元素相關聯的解釋操作, 通常一個直譯器模式中只有一個終結符表示式, 但有多個例項, 對應不同的終結符。 - NonterminalExpression 非終結符表示式:文法中的每條規則對應於一個非終結表示式, 具體到我們的例子就是加減法規則分別對應到 AddExpression 和 SubExpression 兩個類。 非終結符表示式根據邏輯的複雜程度而增加, 原則上每個文法規則都對應一個非終結符表示式。 - Context 環境角色 ### 2.3 通用程式碼 **抽象表示式** ```java public abstract class Expression { abstract Object interpreter(Context ctx); } ``` 抽象表示式比較簡單,通常只有一個方法,但是它是生成語法集合(也叫做語法樹) 的關鍵, 每個語法集合完成指定語法解析任務, 它是通過遞迴呼叫的方式, 最終由最小的語法單元進行解析完成。 **終結符表示式** ```java public class TerminalExpression extends Expression { // 通常終結符表示式只有一個, 但是有多個物件 public Object interpreter(Context context) { return null; } } ``` 終結符表示式比較簡單, 主要是處理場景元素和資料的轉換。 **非終結符表示式** ```java public class NonterminalExpression extends Expression { // 每個非終結符表示式都會對其他表示式產生依賴 public NonterminalExpression(Expression ...expressions) { } @Override Object interpreter(Context ctx) { // 進行文法處理 return null; } } ``` 每個非終結符表示式都代表了一個文法規則, 並且每個文法規則都只關心自己周邊的文法規則的結果(注意是結果) , 因此這就產生了每個非終結符表示式呼叫自己周邊的非終結符表示式, 然後最終、 最小的文法規則就是終結符表示式, 終結符表示式的概念就是如此,不能夠再參與比自己更小的文法運算了。 **客戶端** ```java public class Client { public static void main(String[] args) { Context ctx = new Context(); Stack stack = null; for(int i = 0; i < args.length; i++){ // 進行語法判斷, 併產生遞迴呼叫 } // 產生一個完整的語法樹, 由各個具體的語法分析進行解析 Expression exp = stack.pop(); //具體元素進入場景 exp.interpreter(ctx); } } ``` ### 2.4 優點 直譯器是一個簡單語法分析工具, 它最顯著的優點就是擴充套件性, 修改語法規則只要修改相應的非終結符表示式就可以了, 若擴充套件語法, 則只要增加非終結符類就可以了。 ### 2.5 缺點 - 直譯器模式會引起類膨脹。 - 直譯器模式採用遞迴呼叫方法,將會導致除錯非常複雜。 - 使用了大量的迴圈和遞迴,效率是一個不容忽視的問題。 ## 3. 四則運算 簡單使用直譯器模式實現一下加減法運算。 首先定義一個計算類,用作解析器封裝: ```java public class Calculator { private Expression expression; // 建構函式,傳參並解析 public Calculator(String expStr) { // 安排運算先後順序 Stack stack = new Stack<>(); // 表示式拆分為字元陣列 char[] charArray = expStr.toCharArray(); Expression left = null; Expression right = null; for(int i=0; i var) { return this.expression.interpreter(var); } } ``` 接下來是一個抽象表示式: ```java public abstract class Expression { abstract int interpreter(HashMap var); } ``` 下面是一個變數解析器: ```java public class VarExpression extends Expression { private String key; public VarExpression(String key) { this.key = key; } @Override int interpreter(HashMap var) { return var.get(this.key); } } ``` 變數解析器的主要作用是從 map 中將資料一個一個取出來。 接下來是一個抽象運算子號解析器: ```java public class SymbolExpression extends Expression { protected Expression left; protected Expression right; public SymbolExpression(Expression left, Expression right) { this.left = left; this.right = right; } @Override int interpreter(HashMap var) { return 0; } } ``` 因為是運算子號,而每個運算子號都只和自己左右兩個數字有關係,左右兩個數字有可能也是一個解析的結果,無論何種型別,都是 Expression 類的實現類。 接下來是兩個具體的運算子號解析器,一個加號解析器和一個減號解析器: ```java public class AddExpression extends SymbolExpression { public AddExpression(Expression left, Expression right) { super(left, right); } @Override int interpreter(HashMap var) { return super.left.interpreter(var) + super.right.interpreter(var); } } public class SubExpression extends SymbolExpression { public SubExpression(Expression left, Expression right) { super(left, right); } @Override int interpreter(HashMap var) { return super.left.interpreter(var) - super.right.interpreter(var); } } ``` 最後是我們的客戶端類: ```java public class Client { public static void main(String[] args) throws IOException { String expStr = getExpStr(); HashMap var = getValue(expStr); 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 getValue(String expStr) throws IOException { HashMap 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; } } ``` 執行結果如下: ```java 請輸入表示式:a+b-c 請輸入a的值:10 請輸入b的值:20 請輸入c的值:13 運算結果:a+b-c=17 ``` 直譯器模式在實際的系統開發中使用得非常少, 因為它會引起效率、 效能以及維護等問題, 一般在大中型的框架型專案能夠找到它的身影, 如一些資料分析工具、 報表設計工具、科學計算工具等。 如果遇到確定要使用解析器的場景,可以考慮一下 Expression4J 、 MESP(Math Expression String Parser) 、 Jep 等開源的解析工具包,功能都異常強大,而且非常容易使用,效率也還不錯, 實現大多數的數學運算沒有問題,完全自己沒有必要從頭開始編寫解