1. 程式人生 > >Java設計模式之從[計算器]分析直譯器(Interpreter)模式

Java設計模式之從[計算器]分析直譯器(Interpreter)模式

  直譯器模式是一種廣泛運用於編譯器的設計模式。它的意圖是給定一個語言,定義它的文法的一種表示,並定義一個直譯器來解釋語言中的句子。

  本篇的內容和“遊戲”無關,是從編譯原理的角度來對這個模式進行解釋,因為它幾乎只出現在編譯器中。

  下面簡單介紹一下編譯原理中的文法定義。文法定義可以理解為用類似正則表示式來定義一個文法,它由終結符、非終結符組成。簡單來說,終結符是指的具體的符號,非終結符是指的具有遞迴性質的某類終結符的集合。現在請看一個簡單的計算器的BNF文法定義(不熟悉的同學請查閱編譯原理的文法一章):

L → E

E → E1 + T | T

T → T1 * F | T

F→ ( E ) | digit

digit → 1 | 2 | 3 | 4 |...

  用通俗的話解釋上面的文法定義,就是: L是一個合法的表示式,由E組成,E是由E+T或者T組成,T是由T*F或者T組成,F是由(E)或者digit組成,digit指的就是1、2、3……之類的數字。任何符合這個文法定義的表示式都是合法表示式,如(2*(5+2)-3)*4等。文法的定義包含這遞迴的思想在裡面,對於可遞迴的符號,如L、E、T、F,它們被稱作非終結符,對於digit,它被稱作終結符,因為當編譯器分析到此處就無需遞迴了,直接返回一個具體的值,這個遞迴過程就此終止。

  回到本篇文章的主題,假設我們需要計算一個表示式,可以將它表示為一個抽象的語法樹。例如,假設我們有 +、-、*三個函式,我們需要計算a*b+(b-a),在函數語言程式設計中,我們可以這樣寫:表示式的結果= +( *(a,b), -(b, a) )。其中,+(x,y)表示計算x+y。

  為了實現上述思想,請看下面的Java程式碼:

import java.util.HashMap;
import java.util.Map;

interface Expression{
    int interpret(Context context);
}

class Context {
    private Map<String, Integer> variables = new HashMap<String, Integer>();
    public Integer get(String name){
        return variables.get(name);
    }
    public void add(String name, int value){
        variables.put(name, value);
    }
}

class NumberExpression implements Expression{
    public String name;
    public NumberExpression(String name){
        this.name = name;
    }
    public int interpret(Context context){
        return context.get(name);
    }    
}

class AddExpression implements Expression{
    private Expression left, right;
    public AddExpression(Expression left, Expression right){
        this.left = left;
        this.right = right;
    }
    public int interpret(Context context){
        return left.interpret(context) + right.interpret(context);
    }
}

class SubstractExpression implements Expression{
    private Expression left, right;
    public SubstractExpression(Expression left, Expression right){
        this.left = left;
        this.right = right;
    }
    public int interpret(Context context){
        return left.interpret(context) - right.interpret(context);
    }
}

class MultiplyExpression implements Expression{
    private Expression left, right;
    public MultiplyExpression(Expression left, Expression right){
        this.left = left;
        this.right = right;
    }
    public int interpret(Context context){
        return left.interpret(context) * right.interpret(context);
    }
}

public class Interpreter
{
    public static void main(String[] args) throws InterruptedException {
        int a=5, b=4;
        //計算a*b+(b-a);
        Context context = new Context();
        context.add("a", a);
        context.add("b", b);
        Expression expression = 
                new AddExpression(
                        new MultiplyExpression(new NumberExpression("a"), new NumberExpression("b")),
                        new SubstractExpression(new NumberExpression("b"), new NumberExpression("a"))
                        );
        System.out.println(expression.interpret(context));
    }
}

  我們用Expression介面表示一個表示式的介面,其中的interpret方法表示解釋表示式的含義。那麼,對於非終結符,interpret就呼叫各個表示式的實現方法(如加、減、乘),對於終結符,則直接返回它的值(為一個整數)。在一個表示式中,我們把表示式的整個作用域成為“上下文(Context)”,在這個上下文中儲存著表示式中各個變數的名字和值。因此,我們定義了一個Context類,其中有個HashMap,來儲存變數和它對應的值。在上述例子中,NumberExpression是終結符,因此它的interpret方法就是查詢上下文中的變數,並返回它的值;對於其他Expression,它們的interpret分別表示加法、減法和乘法,利用面向物件的多型性質,呼叫Expression介面的interpret方法來實現計算。

  在main方法中,我們將兩個變數(a、b)加入了上下文context中,由於我們要計算a*b+(b-a),根據括號、乘法優先原則,其實它是計算兩個表示式的加法,因此我們在定義expression的時候,最開始是new了一個AddExpression。其實,直譯器模式就是一個遞迴呼叫的模式,最後呼叫expression.interpret(context)將表示式的結果計算出來。

  由上可見,直譯器模式對於擴充套件文法、實現文法是十分方便的,然而,假設對於文法中的一條規則,我們都必須要新增一個繼承於Expression的類,對於複雜的文法則難以維護,此時應當考慮用其它技術來實現之。