設計模式(二十七)----行為型模式之直譯器模式

2023-03-21 21:00:28

1 概述

如上圖,設計一個軟體用來進行加減計算。我們第一想法就是使用工具類,提供對應的加法和減法的工具方法。

//用於兩個整數相加
public static int add(int a,int b){
    return a + b;
}
​
//用於兩個整數相加
public static int add(int a,int b,int c){
    return a + b + c;
}
​
//用於n個整數相加
public static int add(Integer ... arr) {
    int sum = 0;
    for (Integer i : arr) {
        sum += i;
    }
    return sum;
}

上面的形式比較單一、有限,如果形式變化非常多,這就不符合要求,因為加法和減法運算,兩個運運算元與數值可以有無限種組合方式。比如 1+2+3+4+5、1+2+3-4等等。

顯然,現在需要一種翻譯識別機器,能夠解析由數位以及 + - 符號構成的合法的運算序列。如果把運運算元和數位都看作節點的話,能夠逐個節點的進行讀取解析運算,這就是直譯器模式的思維。

定義:

給定一個語言,定義它的文法表示,並定義一個直譯器,這個直譯器使用該標識來解釋語言中的句子。

在直譯器模式中,我們需要將待解決的問題,提取出規則,抽象為一種「語言」。比如加減法運算,規則為:由數值和+-符號組成的合法序列,「1+3-2」 就是這種語言的句子。

直譯器就是要解析出來語句的含義。但是如何描述規則呢?

文法(語法)規則:

文法是用於描述語言的語法結構的形式規則。

expression ::= value | plus | minus
plus ::= expression ‘+’ expression     1+2
minus ::= expression ‘-’ expression    2-1
value ::= integer

注意: 這裡的符號「::=」表示「定義為」的意思,豎線 | 表示或,左右的其中一個,引號內為字元本身,引號外為語法。

上面規則描述為 :

表示式可以是一個值,也可以是plus或者minus運算,而plus和minus又是由表示式結合運運算元構成,值的型別為整型數。

抽象語法樹:

在電腦科學中,抽象語法樹(AbstractSyntaxTree,AST),或簡稱語法樹(Syntax tree),是原始碼語法結構的一種抽象表示。它以樹狀的形式表現程式語言的語法結構,樹上的每個節點都表示原始碼中的一種結構。

用樹形來表示符合文法規則的句子。

2 結構

直譯器模式包含以下主要角色。

  • 抽象表示式(Abstract Expression)角色:定義直譯器的介面,約定直譯器的解釋操作,主要包含解釋方法 interpret()。

  • 終結符表示式(Terminal Expression)角色:是抽象表示式的子類,用來實現文法中與終結符相關的操作,文法中的每一個終結符都有一個具體終結表示式與之相對應。 上面文法中的value

  • 非終結符表示式(Nonterminal Expression)角色:也是抽象表示式的子類,用來實現文法中與非終結符相關的操作,文法中的每條規則都對應於一個非終結符表示式。

  • 環境(Context)角色:通常包含各個直譯器需要的資料或是公共的功能,一般用來傳遞被所有直譯器共用的資料,後面的直譯器可以從這裡獲取這些值。

  • 使用者端(Client):主要任務是將需要分析的句子或表示式轉換成使用直譯器物件描述的抽象語法樹,然後呼叫直譯器的解釋方法,當然也可以通過環境角色間接存取直譯器的解釋方法。

3 案例實現

【例】設計實現加減法的軟體

程式碼如下:

//抽象角色AbstractExpression
public abstract class AbstractExpression {
    public abstract int interpret(Context context);
}
​
//終結符表示式角色
public class Value extends AbstractExpression {
    private int value;
​
    public Value(int value) {
        this.value = value;
    }
​
    @Override
    public int interpret(Context context) {
        return value;
    }
​
    @Override
    public String toString() {
        return new Integer(value).toString();
    }
}
​
//非終結符表示式角色  加法表示式
public class Plus extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;
​
    public Plus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret(Context context) {
        return left.interpret(context) + right.interpret(context);
    }
​
    @Override
    public String toString() {
        return "(" + left.toString() + " + " + right.toString() + ")";
    }
}
​
///非終結符表示式角色 減法表示式
public class Minus extends AbstractExpression {
    private AbstractExpression left;
    private AbstractExpression right;
​
    public Minus(AbstractExpression left, AbstractExpression right) {
        this.left = left;
        this.right = right;
    }
​
    @Override
    public int interpret(Context context) {
        return left.interpret(context) - right.interpret(context);
    }
​
    @Override
    public String toString() {
        return "(" + left.toString() + " - " + right.toString() + ")";
    }
}
​
//終結符表示式角色 變數表示式
public class Variable extends AbstractExpression {
    private String name;
​
    public Variable(String name) {
        this.name = name;
    }
​
    @Override
    public int interpret(Context ctx) {
        return ctx.getValue(this);
    }
​
    @Override
    public String toString() {
        return name;
    }
}
​
//環境類
public class Context {
    private Map<Variable, Integer> map = new HashMap<Variable, Integer>();
​
    public void assign(Variable var, Integer value) {
        map.put(var, value);
    }
​
    public int getValue(Variable var) {
        Integer value = map.get(var);
        return value;
    }
}
​
//測試類
public class Client {
    public static void main(String[] args) {
        //建立環境物件
        Context context = new Context();
​
        //建立多個變數物件
        Variable a = new Variable("a");
        Variable b = new Variable("b");
        Variable c = new Variable("c");
        Variable d = new Variable("d");
​
        //將變數儲存到環境物件中
        context.assign(a,1);
        context.assign(b,2);
        context.assign(c,3);
        context.assign(d,4);
​
        //獲取抽象語法樹    (a - ((b - c) - d))  從裡往外閱讀
        AbstractExpression expression = new Minus(a,new Minus(new Minus(b,c),d));
​
        //解釋(計算)
        int result = expression.interpret(context);
​
        System.out.println(expression + " = " + result);
    }
}

測試結果

4 優缺點

1,優點:

  • 易於改變和擴充套件文法。

    由於在直譯器模式中使用類來表示語言的文法規則,因此可以通過繼承等機制來改變或擴充套件文法。每一條文法規則都可以表示為一個類,因此可以方便地實現一個簡單的語言。

  • 實現文法較為容易。

    在抽象語法樹中每一個表示式節點類的實現方式都是相似的,這些類的程式碼編寫都不會特別複雜。

  • 增加新的解釋表示式較為方便。

    如果使用者需要增加新的解釋表示式只需要對應增加一個新的終結符表示式或非終結符表示式類,原有表示式類程式碼無須修改,符合 "開閉原則"。

2,缺點:

  • 對於複雜文法難以維護。

    在直譯器模式中,每一條規則至少需要定義一個類,因此如果一個語言套件含太多文法規則,類的個數將會急劇增加,導致系統難以管理和維護。

  • 執行效率較低。

    由於在直譯器模式中使用了大量的迴圈和遞迴呼叫,因此在解釋較為複雜的句子時其速度很慢,而且程式碼的偵錯過程也比較麻煩。

5 使用場景

  • 當語言的文法較為簡單,且執行效率不是關鍵問題時。

  • 當問題重複出現,且可以用一種簡單的語言來進行表達時。

  • 當一個語言需要解釋執行,並且語言中的句子可以表示為一個抽象語法樹的時候。