解釋器模式

一咏连、什么是解釋器模式

解釋器這個(gè)名詞想必大家都不會(huì)陌生搭幻,比如編譯原理中姆吭,一個(gè)算術(shù)表達(dá)式通過(guò)詞法分析器形成詞法單元已艰,而后這些詞法單元再通過(guò)語(yǔ)法分析器構(gòu)建語(yǔ)法分析樹痊末,最終形成一顆抽象的語(yǔ)法分析樹。諸如此類的例子也有很多哩掺,比如編譯器凿叠、正則表達(dá)式等等。

如果一種特定類型的問題發(fā)生的頻率足夠高疮丛,那么可能就值得將該問題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子幔嫂,這樣就可以構(gòu)建一個(gè)解釋器辆它,該解釋器通過(guò)解釋這些句子來(lái)解決該問題誊薄。

就比如正則表達(dá)式,它就是解釋器模型的一種應(yīng)用锰茉,解釋器為正則表達(dá)式定義了一個(gè)文法呢蔫,如何表示一個(gè)特定的正則表達(dá)式,以及如何解釋這個(gè)正則表達(dá)式飒筑。

解釋器模式(Interpreter)片吊,給定一個(gè)語(yǔ)言,定義它的文法的一種表示协屡,并定義一個(gè)解釋器俏脊,這個(gè)解釋器使用該表示來(lái)解釋語(yǔ)言中的句子。UML結(jié)構(gòu)圖如下:

image

其中肤晓,Context是環(huán)境角色爷贫,包含解釋器之外的一些全局信息;AbstractExpression為抽象表達(dá)式补憾,聲明一個(gè)抽象的解釋操作漫萄,這個(gè)接口為抽象語(yǔ)法樹中所有的節(jié)點(diǎn)所共享;TerminalExression為終結(jié)符表達(dá)式盈匾,實(shí)現(xiàn)與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作腾务;NonterminalExpression為非終結(jié)符表達(dá)式,為文法中的非終結(jié)符實(shí)現(xiàn)解釋操作削饵,對(duì)文法中每一條規(guī)則R1岩瘦、R2……Rn都需要一個(gè)具體的非終結(jié)符表達(dá)式類未巫。

1. Context環(huán)境角色

public class Context {

    private String input;
    private String output;

    public String getInput() {
        return input;
    }
    public void setInput(String input) {
        this.input = input;
    }
    public String getOutput() {
        return output;
    }
    public void setOutput(String output) {
        this.output = output;
    }

}

2. 抽象表達(dá)式

抽象表達(dá)式是生成語(yǔ)法集合(語(yǔ)法樹)的關(guān)鍵,每個(gè)語(yǔ)法集合完成指定語(yǔ)法解析任務(wù),它是通過(guò)遞歸調(diào)用的方式,最終由最小的語(yǔ)法單元進(jìn)行解析完成烫沙。

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

3. 終結(jié)符表達(dá)式

通常缆瓣,終結(jié)符表達(dá)式比較簡(jiǎn)單,主要處理場(chǎng)景元素和數(shù)據(jù)的轉(zhuǎn)換耀态。

public class TerminalExpression extends AbstractExpression {

    @Override
    public void Interpret(Context context) {
        System.out.println("終端解釋器");
    }

}

4. 非終結(jié)符表達(dá)式

每個(gè)非終結(jié)符表達(dá)式都代表了一個(gè)文法規(guī)則,并且每個(gè)文法規(guī)則都只關(guān)心自己周邊的文法規(guī)則的結(jié)果,因此這就產(chǎn)生了每個(gè)非終結(jié)符表達(dá)式調(diào)用自己周邊的非終結(jié)符表達(dá)式饼拍,然后最終、最小的文法規(guī)則就是終結(jié)符表達(dá)式田炭。

public class NonterminalExpression extends AbstractExpression {

    @Override
    public void Interpret(Context context) {
        System.out.println("非終端解釋器");
    }

}

5. Client客戶端

其中l(wèi)ist為一個(gè)語(yǔ)法容器师抄,容納一個(gè)具體的表達(dá)式。通常Client是一個(gè)封裝類教硫,封裝的結(jié)果就是傳遞進(jìn)來(lái)一個(gè)規(guī)范語(yǔ)法文件叨吮,解析器分析后產(chǎn)生結(jié)果并返回,避免了調(diào)用者與語(yǔ)法分析器的耦合關(guān)系瞬矩。

public class Client {

    public static void main(String[] args) {
        Context context = new Context();
        List<AbstractExpression> list = new ArrayList<>();

        list.add(new TerminalExpression());
        list.add(new NonterminalExpression());
        list.add(new TerminalExpression());
        list.add(new TerminalExpression());

        for (AbstractExpression abstractExpression : list) {
            abstractExpression.Interpret(context);
        }
    }

}

運(yùn)行結(jié)果如下:

image

二茶鉴、解釋器模式的應(yīng)用

1. 何時(shí)使用

  • 當(dāng)有一個(gè)語(yǔ)言需要解釋執(zhí)行,并且你可將該語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹時(shí)

2. 方法

  • 構(gòu)建語(yǔ)法樹景用,定義終結(jié)符與非終結(jié)符

3. 優(yōu)點(diǎn)

  • 可擴(kuò)展性好

4. 缺點(diǎn)

  • 解釋器模式會(huì)引起類膨脹
  • 解釋器模式采用遞歸調(diào)用方法涵叮,將會(huì)導(dǎo)致調(diào)試非常復(fù)雜
  • 使用了大量的循環(huán)和遞歸,效率是一個(gè)不容忽視的問題

5. 使用場(chǎng)景

  • 可以將一個(gè)需要解釋執(zhí)行的語(yǔ)言中的句子表示為一個(gè)抽象語(yǔ)法樹
  • 一些重復(fù)出現(xiàn)的問題可以用一種簡(jiǎn)單的語(yǔ)言來(lái)表達(dá)
  • 一個(gè)簡(jiǎn)單語(yǔ)法需要解釋的場(chǎng)景

6. 應(yīng)用實(shí)例

  • 編譯器
  • 運(yùn)算表達(dá)式計(jì)算伞插、正則表達(dá)式
  • 機(jī)器人

7. 注意事項(xiàng)

  • 盡量不要在重要的模塊中使用解釋器模式割粮,否則維護(hù)會(huì)是一個(gè)很大的問題

三、解釋器模式的實(shí)現(xiàn)

我們現(xiàn)在通過(guò)解釋器模式來(lái)實(shí)現(xiàn)四則運(yùn)算媚污,如計(jì)算a+b的值舀瓢。UML圖如下:

image

1. 解析器封裝類

使用Calculator構(gòu)造函數(shù)傳參,并解析封裝耗美。這里根據(jù)棧的“先進(jìn)后出”來(lái)安排運(yùn)算的先后順序(主要用在乘除法京髓,這里只寫了加減法比較簡(jiǎn)單)。以加法為例幽歼,Calculator構(gòu)造函數(shù)接收一個(gè)表達(dá)式朵锣,然后把表達(dá)式轉(zhuǎn)換為char數(shù)組,并判斷運(yùn)算符號(hào)甸私,如果是‘+’則進(jìn)行加法運(yùn)算诚些,把左邊的數(shù)(left變量)和右邊的數(shù)(right變量)加起來(lái)即可。

例如a+b-c這個(gè)表達(dá)式,根據(jù)for循環(huán)诬烹,首先被壓入棧中的是a元素生成的VarExpression對(duì)象砸烦,然后判斷到加號(hào)時(shí),把a(bǔ)元素的對(duì)象從棧中pop出來(lái)绞吁,與右邊的數(shù)組b進(jìn)行相加幢痘,而b是通過(guò)當(dāng)前的數(shù)組游標(biāo)下移一個(gè)單元格得來(lái)的(為了防止該元素被再次遍歷,通過(guò)++i的方式跳過(guò)下一遍歷)家破。減法運(yùn)算同理颜说。

public class Calculator {

    //定義表達(dá)式
    private Expression expression;

    //構(gòu)造函數(shù)傳參,并解析
    public Calculator(String expStr) {
        //安排運(yùn)算先后順序
        Stack<Expression> stack = new Stack<>();
        //表達(dá)式拆分為字符數(shù)組
        char[] charArray = expStr.toCharArray();

        Expression left = null;
        Expression right = null;
        for(int i=0; i<charArray.length; i++) {
            switch (charArray[i]) {
            case '+':    //加法
                left = stack.pop();
                right = new VarExpression(String.valueOf(charArray[++i]));
                stack.push(new AddExpression(left, right));
                break;
            case '-':    //減法
                left = stack.pop();
                right = new VarExpression(String.valueOf(charArray[++i]));
                stack.push(new SubExpression(left, right));
                break;
            default:    //公式中的變量
                stack.push(new VarExpression(String.valueOf(charArray[i])));
                break;
            }
        }
        this.expression = stack.pop();
    }

    //計(jì)算
    public int run(HashMap<String, Integer> var) {
        return this.expression.interpreter(var);
    }

}

2. 抽象表達(dá)式類

通過(guò)Map鍵值對(duì)汰聋,使鍵對(duì)應(yīng)公式參數(shù)门粪,如a、b烹困、c等玄妈,值為運(yùn)算時(shí)取得的具體數(shù)值。

public abstract class Expression {

    //解析公式和數(shù)值髓梅,key是公式中的參數(shù)拟蜻,value是具體的數(shù)值
    public abstract int interpreter(HashMap<String, Integer> var);

}

3. 變量解析器

通過(guò)interpreter()方法從map中取之。

public class VarExpression extends Expression {

    private String key;

    public VarExpression(String key) {
        this.key = key;
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        return var.get(this.key);
    }

}

4. 抽象運(yùn)算符號(hào)解析器

這里枯饿,每個(gè)運(yùn)算符合都只和自己左右兩個(gè)數(shù)字有關(guān)系酝锅,但左右兩個(gè)數(shù)字有可能也是一個(gè)解析的結(jié)果,無(wú)論何種類型鸭你,都是Expression類的實(shí)現(xiàn)類屈张。

public class SymbolExpression extends Expression {

    protected Expression left;
    protected Expression right;

    public SymbolExpression(Expression left, Expression right) {
        this.left = left;
        this.right = right;
    }

    @Override
    public int interpreter(HashMap<String, Integer> var) {
        // TODO Auto-generated method stub
        return 0;
    }

}

5. 加法解析器

public class AddExpression extends SymbolExpression {

    public AddExpression(Expression left, Expression right) {
        super(left, right);
    }

    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) + super.right.interpreter(var);
    }

}

6. 減法解析器

public class SubExpression extends SymbolExpression {

    public SubExpression(Expression left, Expression right) {
        super(left, right);
    }

    public int interpreter(HashMap<String, Integer> var) {
        return super.left.interpreter(var) - super.right.interpreter(var);
    }

}

7. Client客戶端

這里就比較簡(jiǎn)單了擒权,通過(guò)getExpStr()方法獲取表達(dá)式袱巨,再通過(guò)getValue()方法獲取值的映射,最后再實(shí)例化Calculator類碳抄,通過(guò)run()方法獲取最終的運(yùn)算結(jié)果愉老。

public class Client {

    public static void main(String[] args) throws IOException {
        String expStr = getExpStr();
        HashMap<String, Integer> var = getValue(expStr);
        Calculator calculator = new Calculator(expStr);
        System.out.println("運(yùn)算結(jié)果:" + expStr + "=" + calculator.run(var));
    }

    //獲得表達(dá)式
    public static String getExpStr() throws IOException {
        System.out.print("請(qǐng)輸入表達(dá)式:");
        return (new BufferedReader(new InputStreamReader(System.in))).readLine();
    }

    //獲得值映射
    public static HashMap<String, Integer> getValue(String expStr) throws IOException {
        HashMap<String, Integer> map = new HashMap<>();

        for(char ch : expStr.toCharArray()) {
            if(ch != '+' && ch != '-' ) {
                if(! map.containsKey(String.valueOf(ch))) {
                    System.out.print("請(qǐng)輸入" + String.valueOf(ch) + "的值:");
                    String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
                    map.put(String.valueOf(ch), Integer.valueOf(in));
                }
            }
        }

        return map;
    }

}

運(yùn)算結(jié)果如下:

image
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市剖效,隨后出現(xiàn)的幾起案子嫉入,更是在濱河造成了極大的恐慌,老刑警劉巖璧尸,帶你破解...
    沈念sama閱讀 217,509評(píng)論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件咒林,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡爷光,警方通過(guò)查閱死者的電腦和手機(jī)垫竞,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人欢瞪,你說(shuō)我怎么就攤上這事活烙。” “怎么了遣鼓?”我有些...
    開封第一講書人閱讀 163,875評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵啸盏,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我骑祟,道長(zhǎng)回懦,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,441評(píng)論 1 293
  • 正文 為了忘掉前任次企,我火速辦了婚禮粉怕,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抒巢。我一直安慰自己贫贝,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,488評(píng)論 6 392
  • 文/花漫 我一把揭開白布蛉谜。 她就那樣靜靜地躺著稚晚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪型诚。 梳的紋絲不亂的頭發(fā)上客燕,一...
    開封第一講書人閱讀 51,365評(píng)論 1 302
  • 那天,我揣著相機(jī)與錄音狰贯,去河邊找鬼也搓。 笑死,一個(gè)胖子當(dāng)著我的面吹牛涵紊,可吹牛的內(nèi)容都是我干的傍妒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼摸柄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼颤练!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起驱负,我...
    開封第一講書人閱讀 39,062評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤嗦玖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后跃脊,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體宇挫,經(jīng)...
    沈念sama閱讀 45,500評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,706評(píng)論 3 335
  • 正文 我和宋清朗相戀三年酪术,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了器瘪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,834評(píng)論 1 347
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖娱局,靈堂內(nèi)的尸體忽然破棺而出彰亥,到底是詐尸還是另有隱情,我是刑警寧澤衰齐,帶...
    沈念sama閱讀 35,559評(píng)論 5 345
  • 正文 年R本政府宣布任斋,位于F島的核電站,受9級(jí)特大地震影響耻涛,放射性物質(zhì)發(fā)生泄漏废酷。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,167評(píng)論 3 328
  • 文/蒙蒙 一抹缕、第九天 我趴在偏房一處隱蔽的房頂上張望澈蟆。 院中可真熱鬧,春花似錦卓研、人聲如沸趴俘。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)寥闪。三九已至,卻和暖如春磨淌,著一層夾襖步出監(jiān)牢的瞬間疲憋,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工梁只, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留缚柳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 47,958評(píng)論 2 370
  • 正文 我出身青樓搪锣,卻偏偏與公主長(zhǎng)得像秋忙,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子淤翔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,779評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • 解釋器模式 案例 張三公司最近需要開發(fā)一款簡(jiǎn)單的加法/減法解釋器翰绊,只要輸入一個(gè)加法/減法表達(dá)式佩谷,它就能夠計(jì)算出表達(dá)...
    Phoegel閱讀 657評(píng)論 0 0
  • 個(gè)人學(xué)習(xí)筆記分享旁壮,當(dāng)前能力有限,請(qǐng)勿貶低谐檀,菜鳥互學(xué)抡谐,大佬繞道如有勘誤,歡迎指出和討論桐猬,本文后期也會(huì)進(jìn)行修正和補(bǔ)充 ...
    Echo_YeZ閱讀 218評(píng)論 0 0
  • 1.定義 給分析對(duì)象定義一個(gè)語(yǔ)言麦撵,并定義該語(yǔ)言的文法表示,再設(shè)計(jì)一個(gè)解析器來(lái)解釋語(yǔ)言中的句子。也就是說(shuō)免胃,用編譯語(yǔ)言...
    CXY_XZL閱讀 366評(píng)論 0 5
  • 原文傳送門 1 介紹 解釋器模式是類的行為模式音五。給定一個(gè)語(yǔ)言之后,解釋器模式可以定義出其文法的一種表示羔沙,并同時(shí)提供...
    dd299閱讀 308評(píng)論 0 1
  • 一躺涝、概念 1、解釋器模式的動(dòng)機(jī) ? 雖然目前計(jì)算機(jī)編程語(yǔ)言有好幾百種扼雏,但有時(shí)候我們還是希望能用一些簡(jiǎn)單的語(yǔ)言來(lái)實(shí)...
    阿餅six閱讀 920評(píng)論 0 0