設計模式(二十)解釋器模式

版權聲明:轉載必須註明本文轉自曉_晨的博客:http://blog.csdn.net/niunai112

目錄

導航

設計模式之六大設計原則
設計模式(一)單例模式
設計模式(二)工廠模式
設計模式(三)策略模式
設計模式(四)適配器模式
設計模式(五)享元模式
設計模式(六)建造者模式
設計模式(七)原型模式
設計模式(八)橋接模式
設計模式(九)外觀模式
設計模式(十)組合模式
設計模式(十一)裝飾器模式
設計模式(十二)代理模式
設計模式(十三)迭代器模式
設計模式(十四)觀察者模式
設計模式(十五)中介者模式
設計模式(十六)命令模式
設計模式(十七)狀態模式
設計模式(十八)訪問者模式
設計模式(十九)責任鏈模式
設計模式(二十)解釋器模式
設計模式(二十一)備忘錄模式
設計模式(二十二)模板模式
設計模式總結篇(爲什麼要學習設計模式,學習設計模式的好處)

前言

LZ認爲在這23個設計模式中最難的就是解釋器模式了,在實際開發過程中也很少會用到這個模式,但LZ這篇文章還是會透徹的理解一遍這個模式,原本LZ是想把解釋器模式留到最後一篇來寫,但是怕放在最後就草草結束了,所有放到前面來,細細的研究一遍這個模式。
解釋器角色:
抽象解釋器(AbstractExpression):具體的解釋任務由各個實現類完成。
終結符表達式(TerminalExpression):實現與文法中的元素相關聯的解釋操作,通常一個解釋器模式中只有一個終結表達式,但有多個實例,對應不同的終結符。
非終結符表達式(NonterminalExpression):文法中的每條規則對應於一個非終結表達式,非終結符表達式根據邏輯的複雜程度而增加,原則上每個文法規則都對應一個非終結符表達式
上下文(Context): 上下文環境類,包含解釋器之外的全局信息
客戶類(Test): 客戶端,解析表達式,構建抽象語法樹,執行具體的解釋操作等.

例子

解釋器模式最好的例子還是計算器,LZ用解釋器模式來構建一個簡單的計算器


/***
 *
 *@Author ChenjunWang
 *@Description:解釋器接口
 *@Date: Created in 16:20 2018/4/17
 *@Modified By:
 *
 */
public interface Expression {
    int interpreter(Context context);//一定會有解釋方法
}
/***
 *
 *@Author ChenjunWang
 *@Description:抽象非終結符表達式
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public abstract class NonTerminalExpression implements Expression{
    Expression e1,e2;
    public NonTerminalExpression(Expression e1, Expression e2){

        this.e1 = e1;
        this.e2 = e2;
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:減法表達式實現類
 *@Date: Created in 16:57 2018/4/17
 *@Modified By:
 *
 */
public class MinusOperation extends NonTerminalExpression {

    public MinusOperation(Expression e1, Expression e2) {
        super(e1, e2);
    }

 //將兩個表達式相減
    @Override
    public int interpreter(Context context) {
        return this.e1.interpreter(context) - this.e2.interpreter(context);
    }
}


/***
 *
 *@Author ChenjunWang
 *@Description:終結符表達式(在這個例子,用來存放數字,或者代表數字的字符)
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public class TerminalExpression implements Expression{

    String variable;
    public TerminalExpression(String variable){

        this.variable = variable;
    }
    @Override
    public int interpreter(Context context) {
        return context.lookup(this);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:
 *@Date: Created in 16:56 2018/4/17
 *@Modified By:
 *
 */
public class PlusOperation extends NonTerminalExpression {

    public PlusOperation(Expression e1, Expression e2) {
        super(e1, e2);
    }

    //將兩個表達式相加
    @Override
    public int interpreter(Context context) {
        return this.e1.interpreter(context) + this.e2.interpreter(context);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:上下文類(這裏主要用來將變量解析成數字【當然一開始要先定義】)
 *@Date: Created in 16:48 2018/4/17
 *@Modified By:
 *
 */
public class Context {
    private Map<Expression, Integer> map = new HashMap<>();

    //定義變量
    public void add(Expression s, Integer value){
        map.put(s, value);
    }
    //將變量轉換成數字
    public int lookup(Expression s){
        return map.get(s);
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:測試類
 *@Date: Created in 13:27 2018/4/8
 *@Modified By:
 *
 */
public class Test {
    public static void main(String[] args) {

        Context context = new Context();
        TerminalExpression a = new TerminalExpression("a");
        TerminalExpression b = new TerminalExpression("b");
        TerminalExpression c = new TerminalExpression("c");
        context.add(a, 4);
        context.add(b, 8);
        context.add(c, 2);

        System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
    }
}

運行結果如下
-----------------------------------
10

UML圖
interpreteruml
看完後,不知道大家理解了沒?不理解是正常的,因爲我當初學的時候,也是一臉懵逼的。如果你到這就理解了的話,我想說,你的思維能力真的很強。下面LZ試着幫大家深入理解這個模式,首先是非終結符和,終結符的理解方式,
非終結符表達式(相當於樹的樹杈):在這個例子中就是相加,相減的表達式,稱爲非終結符,這是非常形象的,因爲當運算遇到這類的表達式的時候,必須先把非終結符的結果計算出來,猶如剝繭一般,一層一層的調用,就比如上面的

new MinusOperation(new PlusOperation(a,b), c).interpreter(context)

這個MinusOperation左邊參數是new PlusOperation(a,b),是非終結符表達式,所以要調用PlusOperation,因爲PlusOperation的左右兩邊都是TerminalExpression,是終結符表達式,所以計算然後返回,到最外面的MinusOperation函數,發現右邊c是終結符表達式,所以可以計算。

終結符表達式(相當於樹的葉子):遇到這個表達式interpreter執行能直接返回結果,不會向下繼續調用。

用構建的語法樹可以直觀的
interpreter
葉子節點即爲終結符,樹杈即爲非終結符,遇到非終結符要繼續往下解析,遇到終結符則返回。a+b-c(4+8-2)

看到這如果你已經懂了的話,那就不必再往下看了,下面是拓展,上面的語法樹是LZ手動建的(new MinusOperation(new PlusOperation(a,b), c).interpreter(context)),實際情況,客戶輸入的都是(4+8-2)這樣的式子,所以,LZ接下來寫的就是解析的輸入式子然後自動構建語法樹,然後計算結果拉。

/***
 *
 *@Author ChenjunWang
 *@Description:
 *@Date: Created in 16:48 2018/4/17
 *@Modified By:
 *
 */
public class Context {
    private Map<Expression, Integer> map = new HashMap<>();

    public void add(Expression s, Integer value){
        map.put(s, value);
    }
    public Integer lookup(Expression s){
        return map.get(s);
    }
    //構建語法樹的主要方法
    public static Expression build(String str) {
        //主要利用棧來實現
        Stack<Expression> objects = new Stack<>();
        for (int i = 0; i < str.length(); i++){
            char c = str.charAt(i);
            //遇到運算符號+號時候
            if (c == '+'){

                //先出棧
                Expression pop = objects.pop();

                //將運算結果入棧
                objects.push(new PlusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));
            } else if (c == '-'){
                //遇到減號類似加號
                Expression pop = objects.pop();

                objects.push(new MinusOperation(pop, new TerminalExpression(String.valueOf(str.charAt(++i)))));

            } else {
                //遇到非終結符直接入棧(基本就是第一個數字的情況)
                objects.push(new TerminalExpression(String.valueOf(str.charAt(i))));
            }
        }
        //把最後的棧頂元素返回
        return objects.pop();
    }
}
/***
 *
 *@Author ChenjunWang
 *@Description:終結符實現類
 *@Date: Created in 16:22 2018/4/17
 *@Modified By:
 *
 */
public class TerminalExpression implements Expression {

    String variable;
    public TerminalExpression(String variable){

        this.variable = variable;
    }
    @Override
    public int interpreter(Context context) {
        //因爲要兼容之前的版本
        Integer lookup = context.lookup(this);
        if (lookup == null)
            //若在map中能找到對應的數則返回
            return Integer.valueOf(variable);
        //找不到則直接返回(認爲輸入的就是數字)
        return lookup;
    }
}

/***
 *
 *@Author ChenjunWang
 *@Description:測試類
 *@Date: Created in 13:27 2018/4/8
 *@Modified By:
 *
 */
public class Test {
    public static void main(String[] args) {

        Context context = new Context();
        TerminalExpression a = new TerminalExpression("a");
        TerminalExpression b = new TerminalExpression("b");
        TerminalExpression c = new TerminalExpression("c");
        String str = "4+8-2+9+9-8";
        Expression build = Context.build(str);
        System.out.println("4+8-2+9+9-8=" + build.interpreter(context));

        context.add(a, 4);
        context.add(b, 8);
        context.add(c, 2);

        System.out.println(new MinusOperation(new PlusOperation(a,b), c).interpreter(context));
    }
}

運行結果如下
-----------------------------------------
4+8-2+9+9-8=20
10

當然LZ實現的這個計算器不是非常的好,只是爲了說明解釋器模式,望各位看官見諒哈,若各位有興趣可以繼續完善這個demo,比如增加乘法和除法,對輸入的數據進行判斷,還有目前對字符串解析的計算肯定只支持(0-9)的數字計算等等,大家可以在這些點上進行拓展。

好了,當你看到這的時候,設計模式的學習已經快到尾聲了,與這篇相比,後面的兩篇沒有太大的難度。各位給自己鼓鼓掌吧!

總結

優點

(1)擴展性強,若要新增乘,除,添加相應的非終結表達式,修改計算邏輯即可。

缺點

(1)需要建大量的類,因爲每一種語法都要建一個非終結符的類。
(2)解釋的時候採用遞歸調用方法,導致有時候函數的深度會很深,影響效率。

Git地址

本篇實例Github地址:https://github.com/stackisok/Design-Pattern/tree/master/src/interpreter

回到最上方


有什麼不懂或者不對的地方,歡迎留言。
喜歡LZ文章的小夥伴們,可以關注一波,也可以留言,LZ會回你們的。
覺得寫得不錯的小夥伴,歡迎轉載,但請附上原文地址,謝謝^_^!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章