1. 程式人生 > 實用技巧 >設計模式——直譯器模式

設計模式——直譯器模式

在軟體開發中,會遇到有些問題多次重複出現,而且有一定的相似性和規律性。如果將它們歸納成一種簡單的表示式(例如:正則表示式等),那麼這些問題例項將是該表示式的一些句子,這樣就可以用 “編譯原理” 中的直譯器模式來實現。

一、直譯器模式基本介紹


【1】直譯器模式(Interpreter Pattern:是指給定一個語言(表示式),定義它的文法的一種表示。並定義一個直譯器,使用該直譯器來解釋語言中的句子(表示式)。
【2】在編譯原理中,一個算術表示式通過詞法分析器形成詞法單元,而後這些詞法單元再通過語法分析器構建語法分析樹,最終形成一顆抽象的語法分析樹。這裡的詞法分析器和語法分析器都可以看做是直譯器。
【3】直譯器模式是一種類行為型模式,其主要優點如下:①、擴充套件性好,由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。②、容易實現,在語法樹中的每個表示式節點類都是相似的,所以實現其文法較為容易。
【4】、直譯器模式的主要缺點如下:①、執行效率較低。直譯器模式中通常使用大量的迴圈和遞迴呼叫,當要解釋的句子較複雜時,其執行速度很慢,且程式碼的除錯過程也比較麻煩。②、會引起類膨脹。直譯器模式中的每條規則至少需要定義一個類,當包含的文法規則很多時,類的個數將急劇增加,導致系統難以管理與維護。③、可應用的場景比較少。在軟體開發中,需要定義語言文法的應用例項非常少,所以這種模式很少被使用到。
【5】、應用場景

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

二、直譯器模式的結構與類圖


直譯器模式包含以下主要角色
【1】抽象表示式(Abstract Expression)角色:定義直譯器的介面,約定直譯器的解釋操作,主要包含解釋方法 interpret()
【2】終結符表示式(Terminal Expression)角色:是抽象表示式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表示式與之相對應。
【3】非終結符表示式(Nonterminal Expression

)角色:也是抽象表示式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應於一個非終結符表示式。
【4】環境(Context)角色:通常包含各個直譯器需要的資料或是公共的功能,一般用來傳遞被所有直譯器共享的資料,後面的直譯器可以從這裡獲取這些值。
【5】客戶端(Client):主要任務是將需要分析的句子或表示式轉換成使用直譯器物件描述的抽象語法樹,然後呼叫直譯器的解釋方法,當然也可以通過環境角色間接訪問直譯器的解釋方法。

三、直譯器模式案例分析


【1】抽象表示式:抽象類表示式,通過 HashMap 鍵值對, 可以獲取到變數的值。

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

【2】終結符表示式

 1 public class Calculator {
 2 
 3     // 定義表示式
 4     private Expression expression;
 5 
 6     // 建構函式傳參,並解析
 7     public Calculator(String expStr) { // expStr = a+b
 8         // 安排運算先後順序
 9         Stack<Expression> stack = new Stack<>();
10         // 表示式拆分成字元陣列 
11         char[] charArray = expStr.toCharArray();// [a, +, b]
12 
13         Expression left = null;
14         Expression right = null;
15         //遍歷我們的字元陣列, 即遍歷  [a, +, b]
16         //針對不同的情況,做處理
17         for (int i = 0; i < charArray.length; i++) {
18             switch (charArray[i]) {
19             case '+': //
20                 left = stack.pop();// 從stack取出left => "a"
21                 right = new VarExpression(String.valueOf(charArray[++i]));// 取出右表示式 "b"
22                 stack.push(new AddExpression(left, right));// 然後根據得到left 和 right 構建 AddExpresson加入stack
23                 break;
24             case '-': // 
25                 left = stack.pop();
26                 right = new VarExpression(String.valueOf(charArray[++i]));
27                 stack.push(new SubExpression(left, right));
28                 break;
29             default: 
30                 //如果是一個 Var 就建立要給 VarExpression 物件,並push到 stack
31                 stack.push(new VarExpression(String.valueOf(charArray[i])));
32                 break;
33             }
34         }
35         //當遍歷完整個 charArray 陣列後,stack 就得到最後Expression
36         this.expression = stack.pop();
37     }
38 
39     public int run(HashMap<String, Integer> var) {
40         //最後將表示式a+b和 var = {a=10,b=20}
41         //然後傳遞給expression的interpreter進行解釋執行
42         return this.expression.interpreter(var);
43     }
44 }

【3】非終結符表示式(抽象):抽象運算子號解析器 這裡,每個運算子號,都只和自己左右兩個數字有關係。

 1 /**
 2  * 抽象運算子號解析器 這裡,每個運算子號,都只和自己左右兩個數字有關係,
 3  * 但左右兩個數字有可能也是一個解析的結果,無論何種型別,都是Expression類的實現類
 4  * 
 5  * @author Administrator
 6  *
 7  */
 8 public class SymbolExpression extends Expression {
 9 
10     protected Expression left;
11     protected Expression right;
12 
13     public SymbolExpression(Expression left, Expression right) {
14         this.left = left;
15         this.right = right;
16     }
17 
18     //因為 SymbolExpression 是讓其子類來實現,因此 interpreter 是一個預設實現
19     @Override
20     public int interpreter(HashMap<String, Integer> var) {
21         // TODO Auto-generated method stub
22         return 0;
23     }
24 }

【4】非終結符表示式(實現一)

 1 // 加法直譯器
 2 public class AddExpression extends SymbolExpression  {
 3 
 4     public AddExpression(Expression left, Expression right) {
 5         super(left, right);
 6     }
 7 
 8     //處理相加
 9     //var 仍然是 {a=10,b=20}..
10     //一會我們debug 原始碼,就ok
11     public int interpreter(HashMap<String, Integer> var) {
12         //super.left.interpreter(var) : 返回 left 表示式對應的值 a = 10
13         //super.right.interpreter(var): 返回right 表示式對應值 b = 20
14         return super.left.interpreter(var) + super.right.interpreter(var);
15     }
16 }

【5】非終結符表示式(實現二)

 1 public class SubExpression extends SymbolExpression {
 2 
 3     public SubExpression(Expression left, Expression right) {
 4         super(left, right);
 5     }
 6 
 7     //求出left 和 right 表示式相減後的結果
 8     public int interpreter(HashMap<String, Integer> var) {
 9         return super.left.interpreter(var) - super.right.interpreter(var);
10     }
11 }

【6】客戶端

 1 public class ClientTest {
 2 
 3     public static void main(String[] args) throws IOException {
 4         // TODO Auto-generated method stub
 5         String expStr = getExpStr(); // a+b
 6         HashMap<String, Integer> var = getValue(expStr);// var {a=10, b=20}
 7         Calculator calculator = new Calculator(expStr);
 8         System.out.println("運算結果:" + expStr + "=" + calculator.run(var));
 9     }
10 
11     // 獲得表示式
12     public static String getExpStr() throws IOException {
13         System.out.print("請輸入表示式:");
14         return (new BufferedReader(new InputStreamReader(System.in))).readLine();
15     }
16 
17     // 獲得值對映
18     public static HashMap<String, Integer> getValue(String expStr) throws IOException {
19         HashMap<String, Integer> map = new HashMap<>();
20 
21         for (char ch : expStr.toCharArray()) {
22             if (ch != '+' && ch != '-') {
23                 if (!map.containsKey(String.valueOf(ch))) {
24                     System.out.print("請輸入" + String.valueOf(ch) + "的值:");
25                     String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
26                     map.put(String.valueOf(ch), Integer.valueOf(in));
27                 }
28             }
29         }
30 
31         return map;
32     }
33 }