初識設計模式 – 解釋器模式

簡介

在某些情況下,為了更好地描述某一些特定類型的問題,我們可以創建一種新的語言,這種語言擁有自己的表達式和結構,即文法規則。

解釋器設計模式(Interpreter Design Pattern)描述了如何為簡單的語言定義一個文法,如何在該語言中表示一個句子,以及如何解釋這些句子。

因此,解釋器模式的定義是,為某個語言定義它的語法(文法)表示,並定義一個解釋器用來處理這個語法。

典型實現

首先,需要定義一個抽象表達式類,其聲明了抽象的解釋操作,其程式碼示例如下:

public abstract class AbstractExpression {
    public abstract void interpret(Context context);
}

終結符表達式是抽象表達式的子類,它實現了與文法中的終結符相關聯的解釋操作,在句子中的每一個終結符都是該類的一個實例。其程式碼示例如下:

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

非終結符表達式類相對比較複雜,由於在非終結符表達式中可以包含終結符表達式,也可以繼續包含非終結符表達式,因此其解釋操作一般通過遞歸的方式來完成。其程式碼示例如下:

public 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 context) {
        // 遞歸調用每一個組成部分的 interpret() 方法
        // 在遞歸調用時指定組成部分的連接方式,即非終結符的功能
    }
}

通常在解釋器模式中會提供一個環境類用於存儲一些全局資訊,如使用 HashMap 或者 ArrayList 等類型的集合對象,存儲一系列公共資訊,其程式碼示例如下:

public class Context {
    private HashMap<String, String> map = new HashMap<>();

    public void assign(String key, String value) {
        // 往環境中設值
        map.put(key, value);
    }

    public String lookup(String key) {
        // 獲取存儲在環境類中的值
        return map.get(key);
    }
}

總結

優點

解釋器模式的主要優點如下:

  • 易於改變和擴展文法
  • 每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言
  • 實現文法較為容易
  • 增加新的解釋器表達式較為方便

缺點

解釋器模式的主要缺點如下:

  • 對於複雜文法難以維護,增加文法規則會導致類急劇增加,導致系統難以管理和維護
  • 解釋器模式使用了大量循環和遞歸調用,執行效率較低

適用場景

解釋器模式的適用場景如下:

  • 可以將一個需要解釋執行的語言中的句子表示為一個抽象語法樹
  • 一些重複出現的問題可以用一種簡單的語言來進行表達
  • 一個語言的文法較為簡單

源碼

在 JDK 中,java.text.Format 就是一個抽象表達式類的實現,如下是其部分源碼:

public abstract class Format implements Serializable, Cloneable {
    AttributedCharacterIterator createAttributedCharacterIterator(String s) {
        AttributedString as = new AttributedString(s);

        return as.getIterator();
    }

    AttributedCharacterIterator createAttributedCharacterIterator(
                       AttributedCharacterIterator[] iterators) {
        AttributedString as = new AttributedString(iterators);

        return as.getIterator();
    }
}