意圖(Intent)#

給定一個語言,定義其文法的表示法,以及一個使用該表示法來解釋語言中句子的直譯器。

動機(Motivation)#

當某類問題頻繁出現時,可以將問題的實例表達為一種簡單語言中的句子,然後建立一個直譯器來解釋這些句子以解決問題。

正規表達式(regular expression) 為例:與其為每個模式撰寫客製搜尋演算法,不如將模式表達為正規表達式,再由搜尋演算法來解釋它。

Interpreter 模式的做法:

  • 用一個類別代表文法中的每條規則
  • 規則右側的符號成為該類別的實例變數
  • 每個類別實作一個 Interpret 操作,遞迴地解釋子表達式

例如正規表達式的文法可拆為五個類別:抽象的 RegularExpression 及四個子類別——LiteralExpressionAlternationExpressionSequenceExpressionRepetitionExpression。每個正規表達式由這些類別的實例組成的抽象語法樹(abstract syntax tree, AST) 表示。

Interpreter 模式的本質是:每條文法規則對應一個類別,語言中的句子由這些類別的實例組成 AST,透過遞迴呼叫 Interpret 來解釋整棵樹。

適用場景(Applicability)#

當存在一個需要解釋的語言,且可以將語句表示為抽象語法樹時,特別適合以下情況:

  • 文法簡單——文法複雜時,類別階層會變得龐大且難以管理,此時 parser generator 更為適合
  • 效率非首要考量——最高效的直譯器通常不直接解釋 parse tree,而是先轉換為其他形式(如正規表達式轉為狀態機);但轉換器本身仍可用 Interpreter 模式實作

結構(Structure)#

Client 建構(或接收)由 TerminalExpressionNonterminalExpression 實例組成的 AST。Client 初始化 Context 並呼叫根節點的 Interpret。每個 NonterminalExpression 將 Interpret 委派給子表達式,TerminalExpression 定義遞迴的基本情況。

classDiagram
    class AbstractExpression {
        <<interface>>
        +Interpret(Context)
    }
    class TerminalExpression {
        +Interpret(Context)
    }
    class NonterminalExpression {
        -expressions
        +Interpret(Context)
    }
    class Context
    class Client
    AbstractExpression <|.. TerminalExpression
    AbstractExpression <|.. NonterminalExpression
    NonterminalExpression o--> AbstractExpression
    Client --> AbstractExpression
    Client --> Context

參與者(Participants)#

參與者範例職責
AbstractExpressionRegularExpression宣告所有 AST 節點共有的抽象 Interpret 操作
TerminalExpressionLiteralExpression實作與文法中終端符號相關的 Interpret;句子中的每個終端符號都需要一個實例
NonterminalExpressionAlternationExpression、RepetitionExpression、SequenceExpression文法中每條規則 R ::= R1 R2 … Rn 對應一個類別;維護 AbstractExpression 型別的實例變數,對應 R1 到 Rn;Interpret 通常遞迴呼叫子表達式的 Interpret
Context-包含直譯器全域資訊
Client-建構 AST 並呼叫 Interpret

協作方式(Collaborations)#

  • Client 建構 AST,初始化 Context,呼叫 Interpret
  • 每個 NonterminalExpression 節點以子表達式的 Interpret 來定義自己的 Interpret
  • TerminalExpression 的 Interpret 定義遞迴的基本情況
  • 各節點的 Interpret 使用 Context 來儲存與存取直譯器的狀態

優缺點(Consequences)#

  • 容易修改與擴展文法——因為使用類別表示規則,可利用繼承來修改或擴展文法,新表達式可作為舊表達式的變體定義
  • 實作文法相對容易——AST 節點類別的實作方式類似,且常可用 compiler 或 parser generator 自動產生
  • 複雜文法難以維護——每條規則至少對應一個類別,規則越多,類別越龐大,此時應考慮其他技術
  • 容易新增解釋方式——例如 pretty printing 或 type checking,只需在表達式類別上定義新操作;若頻繁新增解釋方式,可改用 Visitor 模式避免修改文法類別

當文法規則超過一定數量時,Interpreter 模式會導致類別爆炸。此時應評估是否改用 parser generator 等工具,而非堅持用此模式。

實作要點(Implementation)#

  • 建構 AST——Interpreter 模式不處理 parsing;AST 可由 table-driven parser、手寫的 recursive descent parser 或客戶端直接建構
  • 定義 Interpret 操作的位置——若頻繁新增解釋方式,可使用 Visitor 模式將 Interpret 放在獨立的 visitor 物件中,避免修改每個文法類別
  • 使用 Flyweight 共享終端符號——句子中多次出現的終端符號(如程式變數名稱)可共享同一個實例。終端節點通常不儲存位置資訊,父節點在解釋時傳入所需的 context,因此可區分 shared(intrinsic)與 passed-in(extrinsic)狀態,適用 Flyweight 模式

實務上 Interpreter 最常見的應用是搭配其他工具使用:用 parser generator 處理 parsing,用 Interpreter 模式處理 AST 的解釋邏輯。不必堅持全部手寫。

已知應用(Known Uses)#

  • Smalltalk 編譯器等以物件導向語言實作的編譯器廣泛使用此模式
  • SPECTalk 用此模式解釋輸入檔案格式的描述
  • QOCA 約束求解工具包用此模式評估約束條件
  • 廣義而言,幾乎每個使用 Composite 模式的地方都包含 Interpreter 模式的影子,但應保留此名稱給那些「將類別階層視為語言」的場景

相關模式(Related Patterns)#

  • Composite:AST 本身就是 Composite 模式的實例
  • Flyweight:展示如何在 AST 中共享終端符號
  • Iterator:直譯器可使用 Iterator 來遍歷結構
  • Visitor:可將各節點的行為集中到單一 visitor 類別中維護