設計模式之解釋器模式(十五)

??解釋器模式(interpreter給定一個語言,定義它的文法的一種表示,并定義一個解釋器,這個解釋器用來解釋語言中的句子。
??這里提到的文法和句子的概念同編譯原理中的描述相同,“文法”指語言的語法規(guī)則,而“句子”是語言集中的元素。

比如我們常常會在字符串中搜索匹配的字符或判斷一個字符串是否符合我們的規(guī)則,此時一般我們會用什么技術?

??如判斷email、匹配電話號碼等。我們會用到正則表達式,而所謂解釋器模式,正則表達式就是它的一種應用,解釋器為正則表達式定義了一個文法,如何表示一個特定的正則表達式,以及如何解釋這個正則表達式。


一、解釋器模式類圖


二、解釋器模式角色

  • 抽象表達式 (AbstractExpression)

??定義解釋器接口,解釋器的解釋操作,主要包含interpret()方法。

  • 終結符表達式 (TermianExpression)

??實現與文法中的終結符相關聯的解釋操作。實現抽象表達式所要求的接口。文法中的每一個終結符都有一個具體終結表達式與之相對應。

  • 非終結符表達式 (NonterminalExpression)

??用來實現文法中與終結符相關的操作,文法中的每條規(guī)則都對應一個非終結符表達式。

  • 環(huán)境角色(Context)

??通常包含各個解釋器需要的數據或公共的功能。

  • 客戶端 (Client)

??客戶端代碼,構建表示該文法定義的語言中一個特定的句子的抽象語法樹,調用解釋操作。


三、小例子

這是一個加減乘除的小例子。

  • 抽象表達式 (AbstractExpression)
//抽象的解釋器,他提供了元素與元素之間的計算方式的類以及真正的計算方式。
public interface Node {
     public int interpret();
}
  • 終結符表達式 (TermianExpression)
 //終結解釋器。就是為了返回最終的值。這個node都能夠代表數學表達式中數字部分的展現。
 public class ValueNode implements Node{
 
   private int value;
 
   public ValueNode( int value) {
        this.value = value;
   }
 
  @Override
  public int interpret() {
       return this.value;
  }

}
  • 非終結符表達式 (NonterminalExpression)
 //非終結解釋器,他需要關聯我們的node,
 public abstract class SymbolNode implements Node{
 
 //分為左node和右node。比如 2*3.2在左邊,3在右邊。2和3同
 //時輸入乘法的node。
 Node left;
 Node right;
 
 public SymbolNode(Node left,Node right) {
   this.left = left;
   this.right = right;
}
}
 public class MulNode extends SymbolNode{
 
  public MulNode(Node left, Node right) {
        super(left, right);
  }
 
 @Override
 public int interpret() {
       return left.interpret() * right.interpret();
}
}
 public class DivNode extends SymbolNode{
 
 public DivNode(Node left, Node right) {
       super(left, right);
 }
 
 @Override
  public int interpret() {
       return left.interpret() / right.interpret();
}
}
 public class ModNode extends SymbolNode{
 
 public ModNode(Node left, Node right) {
       super(left, right);
 }
 
  @Override
  public int interpret() {
      return left.interpret() % right.interpret();
 }
}
  • 核心處理類
/**
  *  結合了Context類,并且為客戶端提供了統(tǒng)一調用接口
 *  這個類是我們的解釋器核心
 */
 public class Calculator {
 private Node node;
 private String statement;
 /**
  *  build方法,解釋了我們的計算公式,將解釋的結果存入stack,也就是我們的環(huán)境類,最終從stack中取出,轉化成我們的node類,然后執(zhí)行我們的interupte方進行計算。
 */
public void build(String statement){
    //結合了我們的非終結解釋器
    Node left =null;
    Node right =null;
    Stack stack = new Stack(); //提供環(huán)境,存儲一些關系
    //我們最重要將我們的node存儲到 stack中。存儲之前,我們已經確定了表達式的順序,解釋完成的結果。
    String[] statementArr = statement.split(" ");
    for (int i = 0; i < statementArr.length; i++) {
        if (statementArr[i].equalsIgnoreCase("*")) {
            left = (Node)stack.pop();//pop這個方法,顯示棧頂元素并且移除棧頂元素
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new MulNode(left, right));//mutinode代表乘號,left與right代表2 和 3
        }
        else if (statementArr[i].equalsIgnoreCase("/")) {
            left = (Node)stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new DivNode(left, right));//mutinode代表/,left與right代表2 和 3
        }
        else if (statementArr[i].equalsIgnoreCase("%")) {
            left = (Node)stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new ModNode(left, right));//mutinode代表%,left與right代表2 和 3
        }
        else{
            stack.push(new ValueNode(Integer.parseInt(statementArr[i]))); //傳入的數字
        }
    }
    this.node = (Node) stack.pop(); //這個node包含了所有的數字以及所有的符號
}
public int compute(){
    return node.interpret();
}
}
  • 測試類
public class Test {

public static void main(String[] args) {
      String statement = "3 * 2 * 4 / 3 % 5";
      Calculator calculator = new Calculator();
      calculator.build(statement);
      System.out.println(statement+" = "+calculator.compute());
}
}

  • 測試結果
3 * 2 * 4 / 3 % 5 = 3
  • 代碼解析

??這張圖是通過debug的方式進行查看如何計算的,首先會進行劃分(3*2*4/3)%5,在進行劃分((3*2*4)/3)%5是以這種樹形結構進行計算的,先計算里邊內容的值,在用計算的和再去外邊與外邊的數值進行計算,如果大家不是很理解,可以查看GitHub中代碼運行,會更加容易理解。


四、解釋器模式優(yōu)缺點

  • 優(yōu)點

??1、可擴展性比較好,靈活。

??2、增加了新的解釋表達式的方式。

??3、易于實現文法。

  • 缺點

??1、執(zhí)行效率比較低,可利用場景比較少。

??2、對于復雜的文法比較難維護。


五、應用實例

用解釋器模式設計一個搜索音樂的程序。

  • 說明:

??假如我們已知歌手名稱和歌曲名稱,獲取播放歌曲,如果歌曲名稱和歌手名稱不匹配,返回暫時沒有歌曲。

  • 設計步驟

??定義一個抽象表達式(Expression)接口,它包含了解釋方法 interpret(String info)。

??定義一個終結符表達式(Terminal Expression)類,它用集合(Set)類來保存滿足條件的歌手或歌曲名稱,并實現抽象表達式接口中的解釋方法 interpret(Stringinfo),用來判斷被分析的字符串是否是集合中的終結符。

??定義一個非終結符表達式(AndExpressicm)類,它也是抽象表達式的子類,它包含滿足條件的歌手的終結符表達式對象和滿足條件的歌曲的終結符表達式對象,并實現 interpret(String info)方法,用來判斷被分析的字符串是否是滿足條件的歌手中的滿足條件的歌曲。

  • 抽象表達式角色
public interface Expression {

public boolean interpret(String info);
}
  • 終結符表達式 (TermianExpression)
 public class TerminalExpression implements Expression {
 
 // 存儲歌曲名稱和歌手
 private Set<String> set= new HashSet<String>();
 
 public TerminalExpression(String[] data){
     for(int i=0;i<data.length;i++) set.add(data[i]);
 }

@Override
public boolean interpret(String info) {
    if(set.contains(info)){
        return true;
    }
    return false;
}
}
  • 非終結符表達式 (NonterminalExpression)
 public class AndExpression implements Expression {
 
 private Expression person;
 
 private Expression songName;
 
 public AndExpression(Expression person,Expression songName){
    this.person=person;
    this.songName=songName;
}

@Override
public boolean interpret(String info) {
    String s[]=info.split("的");
    return person.interpret(s[0])&&songName.interpret(s[1]);
}

}
  • 環(huán)境角色
 public class Context {
 
 private String [] persons = {"薛之謙","劉德華","陳奕迅"};
 
 private String [] songNames = {"丑八怪","冰雨","十年"};
 
 private Expression personSong;
 
 public Context(){
    Expression person = new TerminalExpression(persons);
    Expression songName = new TerminalExpression(songNames);
    personSong = new AndExpression(person,songName);
}
  public void findSongs(String info){
    boolean ok = personSong.interpret(info);
    if(ok) System.out.println("正在播放"+info+",這首歌曲!");
    else System.out.println(info+",音樂播放器暫時沒有這首音樂!");
  }

}
  • 測試類
 public class Client {
 
 public static void main(String[] args) {
     Context context = new Context();
     context.findSongs("陳奕迅的十年");
     context.findSongs("劉德華的冰雨");
     context.findSongs("薛之謙的丑八怪");
     context.findSongs("周杰倫的青花瓷");
 }

}
  • 測試結果
 正在播放陳奕迅的十年,這首歌曲!
 正在播放劉德華的冰雨,這首歌曲!
 正在播放薛之謙的丑八怪,這首歌曲!
 周杰倫的青花瓷,音樂播放器暫時沒有這首音樂!

六、模式的應用場景

??1、當語言的文法較為簡單,且執(zhí)行效率不是關鍵問題是。
??2、當問題重復出現,且可以用一種簡單的語言來進行表達時。
??3、當一個語言需要解釋執(zhí)行,并且語言中的句子可以表示為一個抽象語法樹的時候。

  • 模式的擴展

??在項目開發(fā)中,如果要對數據表達式進行分析與計算,無須再用解釋器模式進行設計了,Java 提供了以下強大的數學公式解析器:Expression4JMESP(Math Expression String Parser)Jep 等,它們可以解釋一些復雜的文法,功能強大,使用簡單。

??現在以 Jep為例來介紹該工具包的使用方法。JepJava expression parser 的簡稱,即 Java表達式分析器,它是一個用來轉換和計算數學表達式的Java庫。通過這個程序庫,用戶可以以字符串的形式輸入一個任意的公式,然后快速地計算出其結果。而且 Jep 支持用戶自定義變量、常量和函數,它包括許多常用的數學函數和常量。

 import com.singularsys.jep.*;
 public class JepDemo{
 public static void main(String[] args) throws JepException{
     Jep jep=new Jep();
     //定義要計算的數據表達式
     String 存款利息="本金*利率*時間";
     //給相關變量賦值
    jep.addVariable("本金",10000);
    jep.addVariable("利率",0.038);
    jep.addVariable("時間",2);
    jep.parse(存款利息);    //解析表達式
    Object accrual=jep.evaluate();    //計算
    System.out.println("存款利息:"+accrual);
}
}
  • 運行結果
        存款利息:760.0
  • 小結

??解釋器模式使用情況很少,使用時一定要結合業(yè)務進行判斷是否符合這種設計模式。

GitHub地址:
??https://github.com/xiaonongOne/interpreter-test/tree/master


最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容