1. 程式人生 > >設計模式 --- 直譯器模式

設計模式 --- 直譯器模式

1.定義

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

文法:

假設有如下ab開頭ef結尾中間排列N(N>= 0)個cd的字串:

|  abcd...... cdef

隨著N的值具體的字串也不同,假如定義一個符號S,從符號S出發推匯出上述字串,那麼推導式:

| S ::= abA*ef

| A ::= cd

其中符號" ::= " 表示推導;符號" * "表示閉包,意思就是符號A可以有0或N個重複;S和A稱為非終結符號,因為它們能推匯出式子右邊的表示式,同時又因為整個推導式是從S出發的,因此S為初始符號,而abef和cd這些字元不能再被推導我們將之稱為終結符號。像這樣的從一個具體的符號出發,通過不斷地應用一些產生式規則從而生成一個字串的集合,我們將描述這個集合的文法稱為形式文法。

直譯器:

可以將直譯器簡單的理解為一個翻譯機,就是用來翻譯類似"abef"、"abcdef"和"abcdcdef"之類的字串句子。

 

2.使用場景

1)如果某個簡單的語言需要解釋執行而且可以將該語言中的語句表示為一個抽象語法樹,可以考慮。

2)在某些特定領域出現不斷重複的問題時,可以將該領域的問題轉化為一種語法規則下的語句,然後構建直譯器來解釋該語句。

 

3.簡單實現

對算術表示式"m+n-p"(加減法表示式)進行解釋。如果使用直譯器模式,那麼代表數字的m、n、p三個字母可以看成終結符號,而"+"這個算術符號可以當成非終結符號。

//抽象算術運算直譯器
abstract class ArithmeticExp{
    abstract int interpret();
}

//數字直譯器
class NumExp extends ArithmeticExp{
    private int num;

    NumExp(int num){
        this.num = num;
    }

    @Override
    int interpret() {
        return num;
    }
}

//抽象運算子號直譯器
abstract class OperatorExp extends ArithmeticExp{
    //用於儲存運算子號兩邊的數字直譯器
    protected ArithmeticExp exp1,exp2;

    public OperatorExp(ArithmeticExp exp1, ArithmeticExp exp2) {
        this.exp1 = exp1;
        this.exp2 = exp2;
    }
}

//加法運算子直譯器
class AddOpratorExp extends OperatorExp{

    public AddOpratorExp(ArithmeticExp exp1, ArithmeticExp exp2) {
        super(exp1, exp2);
    }

    @Override
    int interpret() {
        return exp1.interpret() + exp2.interpret();
    }
}

//減法運算直譯器
class SubtractionExp extends OperatorExp{

    public SubtractionExp(ArithmeticExp exp1, ArithmeticExp exp2) {
        super(exp1, exp2);
    }

    @Override
    int interpret() {
        return exp1.interpret() - exp2.interpret();
    }
}

//處理業務
class Calculator{
    //宣告一個Stack棧儲存並操作所有相關的直譯器
    private Stack<ArithmeticExp> mStack = new Stack<ArithmeticExp>();

    public Calculator(String exps) {
        //宣告兩個臨時變數 儲存運算子左右兩邊的數字直譯器
        ArithmeticExp exp1,exp2;
        //正則表示式分割字串
        String[] elems = exps.split("\\b");
        for (int i=0 ;i< elems.length;i++){
            switch (elems[i]){
                case "+":
                    //則將棧中的直譯器彈出作為運算子號左邊的直譯器
                    exp1 = mStack.pop();
                    //將運算子號陣列下標下一個元素構造為一個數字直譯器
                    exp2 = new NumExp(Integer.valueOf(elems[++i]));
                    //通過以上兩個數字直譯器構造加法運算直譯器
                    mStack.push(new AddOpratorExp(exp1,exp2));
                    break;
                case "-":
                    exp1 = mStack.pop();
                    exp2 = new NumExp(Integer.valueOf(elems[++i]));
                    //構建減法直譯器
                    mStack.push(new SubtractionExp(exp1,exp2));
                    break;

                    default:
                        //如果是數字則直接構造數字直譯器併入棧
                        mStack.push(new NumExp(Integer.valueOf(elems[i])));
                        break;
            }
        }

    }
    //計算結果
    public int calculate(){
        return mStack.pop().interpret();
    }
}
public class ExpressionMode {
    public static void main(String[] args){
        String exp = "76-12+9-1+10";
        Calculator c = new Calculator(exp);
        System.out.println(exp + " = " + c.calculate());

    }
}

輸出:

 

4.小結

優點:

程式碼靈活,拓展性高

 

缺點:

對於每一條文法都可以對應至少一個直譯器,會生成大量的類,後期維護困難,同時對於過於複雜的文法,構建抽象語法樹會顯得異常繁瑣。