簡說設計模式——解釋器模式

1、什麼是解釋器模式

  解釋器這個名詞想必你們都不會陌生,好比編譯原理中,一個算術表達式經過詞法分析器造成詞法單元,然後這些詞法單元再經過語法分析器構建語法分析樹,最終造成一顆抽象的語法分析樹。諸如此類的例子也有不少,好比編譯器、正則表達式等等。git

  若是一種特定類型的問題發生的頻率足夠高,那麼可能就值得將該問題的各個實例表述爲一個簡單語言中的句子,這樣就能夠構建一個解釋器,該解釋器經過解釋這些句子來解決該問題。正則表達式

  就好比正則表達式,它就是解釋器模型的一種應用,解釋器爲正則表達式定義了一個文法,如何表示一個特定的正則表達式,以及如何解釋這個正則表達式。express

  解釋器模式(Interpreter),給定一個語言,定義它的文法的一種表示,並定義一個解釋器,這個解釋器使用該表示來解釋語言中的句子。UML結構圖以下:數組

  其中,Context是環境角色,包含解釋器以外的一些全局信息;AbstractExpression爲抽象表達式,聲明一個抽象的解釋操做,這個接口爲抽象語法樹中全部的節點所共享;TerminalExression爲終結符表達式,實現與文法中的終結符相關聯的解釋操做;NonterminalExpression爲非終結符表達式,爲文法中的非終結符實現解釋操做,對文法中每一條規則R一、R2……Rn都須要一個具體的非終結符表達式類。ide

  1. Context環境角色

 1 public class Context {
 2 
 3     private String input;
 4     private String output;
 5     
 6     public String getInput() {
 7         return input;
 8     }
 9     public void setInput(String input) {
10         this.input = input;
11     }
12     public String getOutput() {
13         return output;
14     }
15     public void setOutput(String output) {
16         this.output = output;
17     }
18     
19 }

  2. 抽象表達式

  抽象表達式是生成語法集合(語法樹)的關鍵,每一個語法集合完成指定語法解析任務,它是經過遞歸調用的方式,最終由最小的語法單元進行解析完成。函數

1 public abstract class AbstractExpression {
2     public abstract void Interpret(Context context);
3 }

  3. 終結符表達式

  一般,終結符表達式比較簡單,主要處理場景元素和數據的轉換。this

1 public class TerminalExpression extends AbstractExpression {
2 
3     @Override
4     public void Interpret(Context context) {
5         System.out.println("終端解釋器");
6     }
7 
8 }

  4. 非終結符表達式

  每一個非終結符表達式都表明了一個文法規則,而且每一個文法規則都只關心本身周邊的文法規則的結果,所以這就產生了每一個非終結符表達式調用本身周邊的非終結符表達式,而後最終、最小的文法規則就是終結符表達式。spa

1 public class NonterminalExpression extends AbstractExpression {
2 
3     @Override
4     public void Interpret(Context context) {
5         System.out.println("非終端解釋器");
6     }
7 
8 }

  5. Client客戶端

  其中list爲一個語法容器,容納一個具體的表達式。一般Client是一個封裝類,封裝的結果就是傳遞進來一個規範語法文件,解析器分析後產生結果並返回,避免了調用者與語法分析器的耦合關係。調試

 1 public class Client {
 2     
 3     public static void main(String[] args) {
 4         Context context = new Context();
 5         List<AbstractExpression> list = new ArrayList<>();
 6         
 7         list.add(new TerminalExpression());
 8         list.add(new NonterminalExpression());
 9         list.add(new TerminalExpression());
10         list.add(new TerminalExpression());
11         
12         for (AbstractExpression abstractExpression : list) {
13             abstractExpression.Interpret(context);
14         }
15     }
16 
17 }

  運行結果以下:code

  

2、解釋器模式的應用

  1. 什麼時候使用

  • 當有一個語言須要解釋執行,而且你可將該語言中的句子表示爲一個抽象語法樹時

  2. 方法

  • 構建語法樹,定義終結符與非終結符

  3. 優勢

  • 可擴展性好

  4. 缺點

  • 解釋器模式會引發類膨脹
  • 解釋器模式採用遞歸調用方法,將會致使調試很是複雜
  • 使用了大量的循環和遞歸,效率是一個不容忽視的問題

  5. 使用場景

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

  6. 應用實例

  • 編譯器
  • 運算表達式計算、正則表達式
  • 機器人

  7. 注意事項

  • 儘可能不要在重要的模塊中使用解釋器模式,不然維護會是一個很大的問題

3、解釋器模式的實現

  咱們如今經過解釋器模式來實現四則運算,如計算a+b的值。UML圖以下:

  1. 解析器封裝類

  使用Calculator構造函數傳參,並解析封裝。這裏根據棧的「先進後出」來安排運算的前後順序(主要用在乘除法,這裏只寫了加減法比較簡單)。以加法爲例,Calculator構造函數接收一個表達式,而後把表達式轉換爲char數組,並判斷運算符號,若是是‘+’則進行加法運算,把左邊的數(left變量)和右邊的數(right變量)加起來便可。

  例如a+b-c這個表達式,根據for循環,首先被壓入棧中的是a元素生成的VarExpression對象,而後判斷到加號時,把a元素的對象從棧中pop出來,與右邊的數組b進行相加,而b是經過當前的數組遊標下移一個單元格得來的(爲了防止該元素被再次遍歷,經過++i的方式跳過下一遍歷)。減法運算同理。

 1 public class Calculator {
 2     
 3     //定義表達式
 4     private Expression expression;
 5     
 6     //構造函數傳參,並解析
 7     public Calculator(String expStr) {
 8         //安排運算前後順序
 9         Stack<Expression> stack = new Stack<>();
10         //表達式拆分爲字符數組
11         char[] charArray = expStr.toCharArray();
12         
13         Expression left = null;
14         Expression right = null;
15         for(int i=0; i<charArray.length; i++) {
16             switch (charArray[i]) {
17             case '+':    //加法
18                 left = stack.pop();
19                 right = new VarExpression(String.valueOf(charArray[++i]));
20                 stack.push(new AddExpression(left, right));
21                 break;
22             case '-':    //減法
23                 left = stack.pop();
24                 right = new VarExpression(String.valueOf(charArray[++i]));
25                 stack.push(new SubExpression(left, right));
26                 break;
27             default:    //公式中的變量
28                 stack.push(new VarExpression(String.valueOf(charArray[i])));
29                 break;
30             }
31         }
32         this.expression = stack.pop();
33     }
34     
35     //計算
36     public int run(HashMap<String, Integer> var) {
37         return this.expression.interpreter(var);
38     }
39 
40 }

  2. 抽象表達式類

  經過Map鍵值對,使鍵對應公式參數,如a、b、c等,值爲運算時取得的具體數值。

1 public abstract class Expression {
2     
3     //解析公式和數值,key是公式中的參數,value是具體的數值
4     public abstract int interpreter(HashMap<String, Integer> var);
5 
6 }

  3. 變量解析器

  經過interpreter()方法從map中取之。

 1 public class VarExpression extends Expression {
 2     
 3     private String key;
 4     
 5     public VarExpression(String key) {
 6         this.key = key;
 7     }
 8 
 9     @Override
10     public int interpreter(HashMap<String, Integer> var) {
11         return var.get(this.key);
12     }
13 
14 }

  4. 抽象運算符號解析器

  這裏,每一個運算符合都只和本身左右兩個數字有關係,但左右兩個數字有可能也是一個解析的結果,不管何種類型,都是Expression類的實現類。

 1 public class SymbolExpression extends Expression {
 2     
 3     protected Expression left;
 4     protected Expression right;
 5 
 6     public SymbolExpression(Expression left, Expression right) {
 7         this.left = left;
 8         this.right = right;
 9     }
10 
11     @Override
12     public int interpreter(HashMap<String, Integer> var) {
13         // TODO Auto-generated method stub
14         return 0;
15     }
16     
17 }

  5. 加法解析器

 1 public class AddExpression extends SymbolExpression {
 2 
 3     public AddExpression(Expression left, Expression right) {
 4         super(left, right);
 5     }
 6     
 7     public int interpreter(HashMap<String, Integer> var) {
 8         return super.left.interpreter(var) + super.right.interpreter(var);
 9     }
10 
11 }

  6. 減法解析器

 1 public class SubExpression extends SymbolExpression {
 2     
 3     public SubExpression(Expression left, Expression right) {
 4         super(left, right);
 5     }
 6     
 7     public int interpreter(HashMap<String, Integer> var) {
 8         return super.left.interpreter(var) - super.right.interpreter(var);
 9     }
10 
11 }

  7. Client客戶端

  這裏就比較簡單了,經過getExpStr()方法獲取表達式,再經過getValue()方法獲取值的映射,最後再實例化Calculator類,經過run()方法獲取最終的運算結果。

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

  運算結果以下:

  

  

  源碼地址:https://gitee.com/adamjiangwh/GoF 

相關文章
相關標籤/搜索