1. 程式人生 > >直譯器模式--Interpreter Pattern

直譯器模式--Interpreter Pattern

最近和同事聊到安卓中必須要用直譯器對資料封裝後才能被用到view中,想想設計模式中也有直譯器模式,筆記記錄。

背景

       如果在系統中某一特定型別的問題發生的頻率很高,此時可以考慮將這些問題的例項表述為一個語言中的句子,因此可以構建一個直譯器,該直譯器通過解釋這些句子來解決這些問題。
      直譯器模式描述瞭如何構成一個簡單的語言直譯器,主要應用在使用面嚮物件語言開發的編譯器中。



在直譯器模式結構圖中包含如下幾個角色: 
● AbstractExpression(抽象表示式):在抽象表示式中聲明瞭抽象的解釋操作,它是所有終結符表示式和非終結符表示式的公共父類。 


● TerminalExpression(終結符表示式):終結符表示式是抽象表示式的子類,它實現了與文法中的終結符相關聯的解釋操作,在句子中的每一個終結符都是該類的一個例項。通常在一個直譯器模式中只有少數幾個終結符表示式類,它們的例項可以通過非終結符表示式組成較為複雜的句子。 
● NonterminalExpression(非終結符表示式):非終結符表示式也是抽象表示式的子類,它實現了文法中非終結符的解釋操作,由於在非終結符表示式中可以包含終結符表示式,也可以繼續包含非終結符表示式,因此其解釋操作一般通過遞迴的方式來完成。 
● Context(環境類):環境類又稱為上下文類,它用於儲存直譯器之外的一些全域性資訊,通常它臨時儲存了需要解釋的語句。 

在直譯器模式中,每一種終結符和非終結符都有一個具體類與之對應,正因為使用類來表示每一條文法規則,所以系統將具有較好的靈活性和可擴充套件性。對於所有的終結符和非終結符,我們首先需要抽象出一個公共父類,即抽象表示式類,其典型程式碼如下所示:

class TerminalExpression extends  AbstractExpression {
       public  void interpret(Context ctx) {
              //終結符表示式的解釋操作
       }
}


class TerminalExpression extends  AbstractExpression {
       public  void interpret(Context ctx) {
              //終結符表示式的解釋操作
       }
}

class NonterminalExpression extends  AbstractExpression {
       private  AbstractExpression left;
       private  AbstractExpression right;

       public  NonterminalExpression(AbstractExpression left,AbstractExpression right) {
              this.left=left;
              this.right=right;
       }

       public void interpret(Context ctx) {
              //遞迴呼叫每一個組成部分的interpret()方法
              //在遞迴呼叫時指定組成部分的連線方式,即非終結符的功能
       }     
}


class Context {
     private HashMap map = new HashMap();
     public void assign(String key, String value) {
         //往環境類中設值
     }
     public String  lookup(String key) {
         //獲取儲存在環境類中的值
     }
}


維基百科例子(四則運算)
1.例子中解析語法使用到stack構建表示式

2.表示式依賴表示式最後以遞迴形式進行計算。

直譯器模式的應用 
 直譯器模式的優點 
      直譯器是一個簡單語法分析工具,它最顯著的優點就是擴充套件性,修改語法規則只要修改相應的非終結符表示式就可以了,若擴充套件語法,則只要增加非終結符類就可以了。  
 

直譯器模式的缺點 
  首先,直譯器模式會引起類膨脹。每個語法都要產生一個非終結符表示式,語法規則比較複雜時,就可能產生大量的類檔案,為維護帶來了非常多的麻煩。 

      其次,直譯器模式採用遞迴呼叫方法。每個非終結符表示式只關心與自己有關的表示式,每個表示式需要知道最終的結果,必須一層一層的剝繭,無論是面向過程的語言還是面向物件的語言,遞迴都是在必要條件下使用的,它導致除錯非常複雜,想想看,如果我們要排查一個語法錯誤,我們是不是要一個一個斷點的除錯下去直到最小的語法單元。 
      最後,效率問題,直譯器模式由於使用了大量的迴圈和遞迴,效率是個不容忽視的問題,特別是用於解析複雜、冗長的語法時,效率是難以忍受的。 

 
 直譯器模式使用的場景 
      一些重複發生的問題可以使用直譯器模式。例如,多個應用伺服器,每天產生大量的日誌,需要對日誌檔案進行分析處理,由於各個伺服器的日誌格式不同,但是資料元素都是相同的,按照直譯器的說法就是終結符表示式都是相同的,但是非終結符表示式就需要制定了,在這種情況下,可以通過程式來一勞永逸的解決該問題。 
        一個簡單語法需要解釋的場景。為什麼是簡單?看看非終結表示式,文法規則越多,複雜度越高,而且類間還要進行遞迴呼叫(看看我們例子中的堆疊),不是一般的複雜,想想看多個類之間的呼叫你需要什麼樣的耐心和信心去排查問題。因此,直譯器模式一般用來解析比較標準的字元,例如 SQL語法分析,不過該部分逐漸被專用工具所取代。 
       在某些特用的商業環境下也會採用直譯器模式,我們剛剛的例子就是一個商業環境,而且現在模型運算的例子非常多,原因就是目前很多商業機構已經能夠提供出大量的資料進行分析了。 
 
 直譯器模式的注意事項 
儘量不要在專案中使用直譯器模式(那你還講這麼多!肅靜肅靜,學無止境,畢竟它也是一種設計模式),除非必要,那用什麼來代替呢?可以使用 shell、JRuby、Groovy等指令碼語言來代替,完全可以滿足一些商業的分析過程,我們在一個銀行的分析型專案中就採用 JRuby 進行運算處理,代替了使用直譯器模式的四則運算。