【設(shè)計(jì)模式(15)】行為型模式之解釋器模式

個(gè)人學(xué)習(xí)筆記分享启上,當(dāng)前能力有限,請(qǐng)勿貶低店印,菜鳥(niǎo)互學(xué)冈在,大佬繞道

如有勘誤,歡迎指出和討論按摘,本文后期也會(huì)進(jìn)行修正和補(bǔ)充


前言

在初高中的時(shí)候我們都學(xué)過(guò)語(yǔ)法包券,無(wú)論是漢語(yǔ)還是英語(yǔ)都有自己所規(guī)范的語(yǔ)法(盡管大部分人平時(shí)并不遵守。院峡。兴使。)

比如同桌的你,按照語(yǔ)法照激,表示修飾關(guān)系发魄,為中心詞,同桌為定語(yǔ)

規(guī)則定下來(lái)了俩垃,那么我們可以設(shè)計(jì)一個(gè)程序來(lái)識(shí)別這種語(yǔ)法的語(yǔ)句励幼,比如頭禿的程序猿改不完的bug等等口柳。苹粟。。


再舉個(gè)栗子跃闹,對(duì)于算術(shù)式1+(2*3-4)*5

我們需要做的第一件事就是將其從外向里拆開(kāi)嵌削,拆解過(guò)程如下

  1. 1+(2*3-4)*5拆為1(2*3-4)*5,計(jì)算其和
  2. (2*3-4)*5拆為2*3-45望艺,計(jì)算乘積
  3. 2*3-4拆為2*34苛秕,計(jì)算其差
  4. 2*3拆為23,計(jì)算其乘積

然后再對(duì)其進(jìn)行計(jì)算找默,拆解過(guò)程中計(jì)算符號(hào)作為兩個(gè)子句的分隔符

也就是說(shuō)我們先將整個(gè)句子拆解艇劫,再對(duì)子句進(jìn)行拆解,一直如此惩激,直至無(wú)法拆解店煞,再對(duì)每一步的結(jié)果進(jìn)行計(jì)算


我們程序猿使用的編程語(yǔ)言也是如此,機(jī)器并不能識(shí)別Java风钻、Python顷蟀、C等等高級(jí)語(yǔ)言,因此需要編譯器將我們的代碼轉(zhuǎn)換成機(jī)器語(yǔ)言

編譯器只能按照某種規(guī)范去轉(zhuǎn)換骡技,因此我們的代碼也需要準(zhǔn)守相關(guān)的語(yǔ)法

這一過(guò)程在==編譯原理==中有詳盡的介紹衩椒,學(xué)藝不精,在這里不多做解釋


解釋器模式(Interpreter Pattern)提供了評(píng)估語(yǔ)言的語(yǔ)法或表達(dá)式的方式,它屬于行為型模式毛萌。

這種模式實(shí)現(xiàn)了一個(gè)表達(dá)式接口苟弛,該接口解釋一個(gè)特定的上下文。

通常被用在 SQL 解析阁将、符號(hào)處理引擎等膏秫,但在開(kāi)發(fā)中使用場(chǎng)景不多,Java中遇到也可用expression4J 替代做盅。


1.介紹

使用目的:給定一個(gè)語(yǔ)言缤削,定義它的文法表示,并定義一個(gè)解釋器吹榴,這個(gè)解釋器使用該標(biāo)識(shí)來(lái)解釋語(yǔ)言中的句子亭敢。

使用時(shí)機(jī):如果一種特定類型的問(wèn)題發(fā)生的頻率足夠高,那么可能就值得將該問(wèn)題的各個(gè)實(shí)例表述為一個(gè)簡(jiǎn)單語(yǔ)言中的句子图筹。這樣就可以構(gòu)建一個(gè)解釋器帅刀,該解釋器通過(guò)解釋這些句子來(lái)解決該問(wèn)題。

解決問(wèn)題:對(duì)于一些固定文法構(gòu)建一個(gè)解釋句子的解釋器远剩。

實(shí)現(xiàn)方法:構(gòu)建語(yǔ)法樹(shù)扣溺,定義終結(jié)符與非終結(jié)符。并構(gòu)建環(huán)境類瓜晤,包含解釋器之外的一些全局信息锥余,一般是 HashMap

應(yīng)用實(shí)例:編譯器痢掠、運(yùn)算表達(dá)式計(jì)算驱犹。

優(yōu)點(diǎn):

  1. 可擴(kuò)展性比較好,靈活足画。
  2. 增加了新的解釋表達(dá)式的方式雄驹。
  3. 易于實(shí)現(xiàn)簡(jiǎn)單文法。

缺點(diǎn):

  1. 可利用場(chǎng)景比較少锌云,且可以被已封裝好的組件替代荠医。
  2. 對(duì)于復(fù)雜的文法比較難維護(hù)吁脱。
  3. 解釋器模式會(huì)引起類膨脹桑涎,每個(gè)文法都必須添加一個(gè)類。
  4. 解釋器模式采用遞歸調(diào)用方法兼贡,控制不當(dāng)可能出現(xiàn)內(nèi)存占用過(guò)大甚至溢出攻冷。

使用場(chǎng)景:

  1. 語(yǔ)言文法較為簡(jiǎn)單,且不苛求執(zhí)行效率的情況下遍希。
  2. 一些重復(fù)出現(xiàn)的問(wèn)題可以用一種簡(jiǎn)單的語(yǔ)言來(lái)進(jìn)行表達(dá)等曼。
  3. 一個(gè)簡(jiǎn)單語(yǔ)法需要解釋,并且語(yǔ)言中的句子可以表示為一個(gè)抽象語(yǔ)法樹(shù)的時(shí)候,如 XML 文檔解釋


2.結(jié)構(gòu)

解釋器模式包含以下主要角色

  • 抽象表達(dá)式(Abstract Expression)角色:定義解釋器的接口禁谦,約定解釋器的解釋操作胁黑,主要包含解釋方法 interpret()。
  • 終結(jié)符表達(dá)式(Terminal Expression)角色:是抽象表達(dá)式的子類州泊,用來(lái)實(shí)現(xiàn)文法中與終結(jié)符相關(guān)的操作丧蘸,文法中的每一個(gè)終結(jié)符都有一個(gè)具體終結(jié)表達(dá)式與之相對(duì)應(yīng)。
  • 非終結(jié)符表達(dá)式(Nonterminal Expression)角色:也是抽象表達(dá)式的子類遥皂,用來(lái)實(shí)現(xiàn)文法中與非終結(jié)符相關(guān)的操作力喷,文法中的每條規(guī)則都對(duì)應(yīng)于一個(gè)非終結(jié)符表達(dá)式。
  • 環(huán)境(Context)角色:通常包含各個(gè)解釋器需要的數(shù)據(jù)或是公共的功能演训,一般用來(lái)傳遞被所有解釋器共享的數(shù)據(jù)弟孟,后面的解釋器可以從這里獲取這些值。
  • 客戶端(Client):主要任務(wù)是將需要分析的句子或表達(dá)式轉(zhuǎn)換成使用解釋器對(duì)象描述的抽象語(yǔ)法樹(shù)样悟,然后調(diào)用解釋器的解釋方法拂募,當(dāng)然也可以通過(guò)環(huán)境角色間接訪問(wèn)解釋器的解釋方法。
解é????¨?¨????????????????

3.實(shí)現(xiàn)

模板

//抽象表達(dá)式類
interface AbstractExpression
{
    public Object interpret(String info);    //解釋方法
}
//終結(jié)符表達(dá)式類
class TerminalExpression implements AbstractExpression
{
    public Object interpret(String info)
    {
        //對(duì)終結(jié)符表達(dá)式的處理
    }
}
//非終結(jié)符表達(dá)式類
class NonterminalExpression implements AbstractExpression
{
    private AbstractExpression exp1;
    private AbstractExpression exp2;
    public Object interpret(String info)
    {
        //非對(duì)終結(jié)符表達(dá)式的處理
    }
}
//環(huán)境類
class Context
{
    private AbstractExpression exp;
    public Context()
    {
        //數(shù)據(jù)初始化
    }
    public void operation(String info)
    {
        //調(diào)用相關(guān)表達(dá)式類的解釋方法
    }
}

示例

模擬業(yè)務(wù)如下:

  • 限行車牌號(hào)乌奇,需要檢查車牌號(hào)地區(qū)及尾號(hào)
  • 現(xiàn)在僅允許武漢的雙號(hào)車通行
package com.company.designPattern.interpreter;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class InterpreterPatternDemo {
    public static void main(String[] args) {
        Context context = new Context();
        context.checkAccess("鄂A23333");
        context.checkAccess("鄂A88888");
        context.checkAccess("鄂C10086");
    }
}

//抽象表達(dá)式類
interface Expression {
    boolean interpret(String info);
}

//終結(jié)符表達(dá)式類
class TerminalExpression implements Expression {
    private Set<String> set = new HashSet<>();

    public TerminalExpression(String[] data) {
        set.addAll(Arrays.asList(data));
    }

    public boolean interpret(String info) {
        return set.contains(info);
    }
}

//非終結(jié)符表達(dá)式類
class AndExpression implements Expression {
    private Expression head = null;
    private Expression tail = null;

    public AndExpression(Expression head, Expression tail) {
        this.head = head;
        this.tail = tail;
    }

    public boolean interpret(String info) {
        return head.interpret(info.substring(0, 2)) && tail.interpret(info.substring(info.length() - 1));
    }
}

//環(huán)境類
class Context {
    private Expression checkExpression;

    public Context() {
        Expression headExpression = new TerminalExpression(new String[]{"鄂A"});
        Expression tailExpression = new TerminalExpression(new String[]{"0", "2", "4", "6", "8"});
        checkExpression = new AndExpression(headExpression, tailExpression);
    }

    public void checkAccess(String info) {
        if (checkExpression.interpret(info)) {
            System.out.println(info + ":允許通行");
        } else {
            System.out.println(info + ":禁止通行");
        }
    }
}

運(yùn)行結(jié)果

image-20201110165641763

4.擴(kuò)展使用

實(shí)際開(kāi)發(fā)中没讲,Java中提供了Expression4JMESP(Math Expression String Parser)Jep 等組件用以替代大部分情況下的解釋器模式礁苗,功能完善強(qiáng)大且封裝性好

比如使用Jep進(jìn)行借款利息計(jì)算

package com.company.designPattern.interpreter;


import com.singularsys.jep.Jep;
import com.singularsys.jep.JepException;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.Scanner;

public class SimpleTest {
    public static void main(String[] args) throws JepException {
        Jep jep = new Jep();
        BigDecimal capital = new BigDecimal(10000).setScale(2, RoundingMode.HALF_UP);
        BigDecimal rate = new BigDecimal(0.05).setScale(2, RoundingMode.HALF_UP);
        System.out.println("本金:" + capital.doubleValue());
        System.out.println("日利率(%):" + rate.doubleValue());
        System.out.println();
        //定義要計(jì)算的數(shù)據(jù)表達(dá)式
        String expression = "本金*(1+利率*0.01)^(時(shí)間*30)";
        //給相關(guān)變量賦值
        jep.addVariable("本金", capital.doubleValue());
        jep.addVariable("利率", rate.doubleValue());
        while (true) {
            //輸入時(shí)間
            System.out.println("請(qǐng)輸入借款時(shí)間(月):");
            Scanner input = new Scanner(System.in);//創(chuàng)建一個(gè)鍵盤掃描類對(duì)象
            jep.addVariable("時(shí)間", input.nextInt());
            //計(jì)算
            jep.parse(expression);    //解析表達(dá)式
            Object accrual = jep.evaluate();    //計(jì)算
            BigDecimal total = new BigDecimal(accrual.toString()).setScale(2, RoundingMode.HALF_UP);
            System.out.println("總還款:" + total.doubleValue());
            System.out.println("總利息:" + total.subtract(capital).doubleValue());
            System.out.println();
        }
    }
}

運(yùn)行結(jié)果

image-20201110175311529

可以看到Jep已經(jīng)提供了很強(qiáng)大的計(jì)算式解析和計(jì)算功能爬凑,而這個(gè)jar包,僅有400kb试伙。嘁信。。


后記

第三方提供的工具已經(jīng)可以替代很多解釋器的使用場(chǎng)景疏叨,當(dāng)然仍有部分適用的場(chǎng)景潘靖,一切根據(jù)實(shí)際業(yè)務(wù)來(lái)啦


作者:Echo_Ye

WX:Echo_YeZ

Email :echo_yezi@qq.com

個(gè)人站點(diǎn):在搭了在搭了。蚤蔓。卦溢。(右鍵 - 新建文件夾)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市秀又,隨后出現(xiàn)的幾起案子单寂,更是在濱河造成了極大的恐慌,老刑警劉巖吐辙,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件宣决,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡昏苏,警方通過(guò)查閱死者的電腦和手機(jī)尊沸,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門威沫,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人洼专,你說(shuō)我怎么就攤上這事棒掠。” “怎么了屁商?”我有些...
    開(kāi)封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵句柠,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我棒假,道長(zhǎng)溯职,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任帽哑,我火速辦了婚禮谜酒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘妻枕。我一直安慰自己僻族,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布屡谐。 她就那樣靜靜地躺著述么,像睡著了一般。 火紅的嫁衣襯著肌膚如雪愕掏。 梳的紋絲不亂的頭發(fā)上度秘,一...
    開(kāi)封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天,我揣著相機(jī)與錄音饵撑,去河邊找鬼剑梳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛滑潘,可吹牛的內(nèi)容都是我干的垢乙。 我是一名探鬼主播语卤,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼稠茂!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后施籍,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年悔常,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸥昏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片罐旗。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖征字,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情氮昧,我是刑警寧澤,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布澎迎,位于F島的核電站灵份,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜枪眉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望棍鳖。 院中可真熱鬧,春花似錦碗旅、人聲如沸渡处。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)骂蓖。三九已至,卻和暖如春川尖,著一層夾襖步出監(jiān)牢的瞬間登下,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工叮喳, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留被芳,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓馍悟,卻偏偏與公主長(zhǎng)得像畔濒,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子锣咒,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

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