設(shè)計(jì)模式之解釋器模式(十五)

??解釋器模式(interpreter給定一個(gè)語(yǔ)言德澈,定義它的文法的一種表示歇攻,并定義一個(gè)解釋器,這個(gè)解釋器用來(lái)解釋語(yǔ)言中的句子梆造。
??這里提到的文法和句子的概念同編譯原理中的描述相同缴守,“文法”指語(yǔ)言的語(yǔ)法規(guī)則,而“句子”是語(yǔ)言集中的元素镇辉。

比如我們常常會(huì)在字符串中搜索匹配的字符或判斷一個(gè)字符串是否符合我們的規(guī)則屡穗,此時(shí)一般我們會(huì)用什么技術(shù)?

??如判斷email忽肛、匹配電話號(hào)碼等村砂。我們會(huì)用到正則表達(dá)式,而所謂解釋器模式屹逛,正則表達(dá)式就是它的一種應(yīng)用箍镜,解釋器為正則表達(dá)式定義了一個(gè)文法,如何表示一個(gè)特定的正則表達(dá)式煎源,以及如何解釋這個(gè)正則表達(dá)式色迂。


一、解釋器模式類圖


二手销、解釋器模式角色

  • 抽象表達(dá)式 (AbstractExpression)

??定義解釋器接口歇僧,解釋器的解釋操作,主要包含interpret()方法锋拖。

  • 終結(jié)符表達(dá)式 (TermianExpression)

??實(shí)現(xiàn)與文法中的終結(jié)符相關(guān)聯(lián)的解釋操作诈悍。實(shí)現(xiàn)抽象表達(dá)式所要求的接口。文法中的每一個(gè)終結(jié)符都有一個(gè)具體終結(jié)表達(dá)式與之相對(duì)應(yīng)兽埃。

  • 非終結(jié)符表達(dá)式 (NonterminalExpression)

??用來(lái)實(shí)現(xiàn)文法中與終結(jié)符相關(guān)的操作侥钳,文法中的每條規(guī)則都對(duì)應(yīng)一個(gè)非終結(jié)符表達(dá)式。

  • 環(huán)境角色(Context)

??通常包含各個(gè)解釋器需要的數(shù)據(jù)或公共的功能柄错。

  • 客戶端 (Client)

??客戶端代碼舷夺,構(gòu)建表示該文法定義的語(yǔ)言中一個(gè)特定的句子的抽象語(yǔ)法樹(shù)苦酱,調(diào)用解釋操作。


三给猾、小例子

這是一個(gè)加減乘除的小例子疫萤。

  • 抽象表達(dá)式 (AbstractExpression)
//抽象的解釋器,他提供了元素與元素之間的計(jì)算方式的類以及真正的計(jì)算方式。
public interface Node {
     public int interpret();
}
  • 終結(jié)符表達(dá)式 (TermianExpression)
 //終結(jié)解釋器敢伸。就是為了返回最終的值扯饶。這個(gè)node都能夠代表數(shù)學(xué)表達(dá)式中數(shù)字部分的展現(xiàn)。
 public class ValueNode implements Node{
 
   private int value;
 
   public ValueNode( int value) {
        this.value = value;
   }
 
  @Override
  public int interpret() {
       return this.value;
  }

}
  • 非終結(jié)符表達(dá)式 (NonterminalExpression)
 //非終結(jié)解釋器池颈,他需要關(guān)聯(lián)我們的node尾序,
 public abstract class SymbolNode implements Node{
 
 //分為左node和右node。比如 2*3.2在左邊躯砰,3在右邊蹲诀。2和3同
 //時(shí)輸入乘法的node。
 Node left;
 Node right;
 
 public SymbolNode(Node left,Node right) {
   this.left = left;
   this.right = right;
}
}
 public class MulNode extends SymbolNode{
 
  public MulNode(Node left, Node right) {
        super(left, right);
  }
 
 @Override
 public int interpret() {
       return left.interpret() * right.interpret();
}
}
 public class DivNode extends SymbolNode{
 
 public DivNode(Node left, Node right) {
       super(left, right);
 }
 
 @Override
  public int interpret() {
       return left.interpret() / right.interpret();
}
}
 public class ModNode extends SymbolNode{
 
 public ModNode(Node left, Node right) {
       super(left, right);
 }
 
  @Override
  public int interpret() {
      return left.interpret() % right.interpret();
 }
}
  • 核心處理類
/**
  *  結(jié)合了Context類弃揽,并且為客戶端提供了統(tǒng)一調(diào)用接口
 *  這個(gè)類是我們的解釋器核心
 */
 public class Calculator {
 private Node node;
 private String statement;
 /**
  *  build方法,解釋了我們的計(jì)算公式则北,將解釋的結(jié)果存入stack矿微,也就是我們的環(huán)境類,最終從stack中取出尚揣,轉(zhuǎn)化成我們的node類涌矢,然后執(zhí)行我們的interupte方進(jìn)行計(jì)算。
 */
public void build(String statement){
    //結(jié)合了我們的非終結(jié)解釋器
    Node left =null;
    Node right =null;
    Stack stack = new Stack(); //提供環(huán)境快骗,存儲(chǔ)一些關(guān)系
    //我們最重要將我們的node存儲(chǔ)到 stack中娜庇。存儲(chǔ)之前,我們已經(jīng)確定了表達(dá)式的順序方篮,解釋完成的結(jié)果名秀。
    String[] statementArr = statement.split(" ");
    for (int i = 0; i < statementArr.length; i++) {
        if (statementArr[i].equalsIgnoreCase("*")) {
            left = (Node)stack.pop();//pop這個(gè)方法,顯示棧頂元素并且移除棧頂元素
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new MulNode(left, right));//mutinode代表乘號(hào)藕溅,left與right代表2 和 3
        }
        else if (statementArr[i].equalsIgnoreCase("/")) {
            left = (Node)stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new DivNode(left, right));//mutinode代表/匕得,left與right代表2 和 3
        }
        else if (statementArr[i].equalsIgnoreCase("%")) {
            left = (Node)stack.pop();
            int val = Integer.parseInt(statementArr[++i]);
            right = new ValueNode(val);
            stack.push(new ModNode(left, right));//mutinode代表%,left與right代表2 和 3
        }
        else{
            stack.push(new ValueNode(Integer.parseInt(statementArr[i]))); //傳入的數(shù)字
        }
    }
    this.node = (Node) stack.pop(); //這個(gè)node包含了所有的數(shù)字以及所有的符號(hào)
}
public int compute(){
    return node.interpret();
}
}
  • 測(cè)試類
public class Test {

public static void main(String[] args) {
      String statement = "3 * 2 * 4 / 3 % 5";
      Calculator calculator = new Calculator();
      calculator.build(statement);
      System.out.println(statement+" = "+calculator.compute());
}
}

  • 測(cè)試結(jié)果
3 * 2 * 4 / 3 % 5 = 3
  • 代碼解析

??這張圖是通過(guò)debug的方式進(jìn)行查看如何計(jì)算的巾表,首先會(huì)進(jìn)行劃分(3*2*4/3)%5汁掠,在進(jìn)行劃分((3*2*4)/3)%5是以這種樹(shù)形結(jié)構(gòu)進(jìn)行計(jì)算的,先計(jì)算里邊內(nèi)容的值集币,在用計(jì)算的和再去外邊與外邊的數(shù)值進(jìn)行計(jì)算考阱,如果大家不是很理解,可以查看GitHub中代碼運(yùn)行鞠苟,會(huì)更加容易理解乞榨。


四秽之、解釋器模式優(yōu)缺點(diǎn)

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

??1、可擴(kuò)展性比較好姜凄,靈活政溃。

??2、增加了新的解釋表達(dá)式的方式态秧。

??3董虱、易于實(shí)現(xiàn)文法。

  • 缺點(diǎn)

??1申鱼、執(zhí)行效率比較低愤诱,可利用場(chǎng)景比較少。

??2捐友、對(duì)于復(fù)雜的文法比較難維護(hù)淫半。


五、應(yīng)用實(shí)例

用解釋器模式設(shè)計(jì)一個(gè)搜索音樂(lè)的程序匣砖。

  • 說(shuō)明:

??假如我們已知歌手名稱和歌曲名稱科吭,獲取播放歌曲,如果歌曲名稱和歌手名稱不匹配猴鲫,返回暫時(shí)沒(méi)有歌曲对人。

  • 設(shè)計(jì)步驟

??定義一個(gè)抽象表達(dá)式(Expression)接口,它包含了解釋方法 interpret(String info)拂共。

??定義一個(gè)終結(jié)符表達(dá)式(Terminal Expression)類牺弄,它用集合(Set)類來(lái)保存滿足條件的歌手或歌曲名稱,并實(shí)現(xiàn)抽象表達(dá)式接口中的解釋方法 interpret(Stringinfo)宜狐,用來(lái)判斷被分析的字符串是否是集合中的終結(jié)符势告。

??定義一個(gè)非終結(jié)符表達(dá)式(AndExpressicm)類,它也是抽象表達(dá)式的子類抚恒,它包含滿足條件的歌手的終結(jié)符表達(dá)式對(duì)象和滿足條件的歌曲的終結(jié)符表達(dá)式對(duì)象咱台,并實(shí)現(xiàn) interpret(String info)方法,用來(lái)判斷被分析的字符串是否是滿足條件的歌手中的滿足條件的歌曲俭驮。

  • 抽象表達(dá)式角色
public interface Expression {

public boolean interpret(String info);
}
  • 終結(jié)符表達(dá)式 (TermianExpression)
 public class TerminalExpression implements Expression {
 
 // 存儲(chǔ)歌曲名稱和歌手
 private Set<String> set= new HashSet<String>();
 
 public TerminalExpression(String[] data){
     for(int i=0;i<data.length;i++) set.add(data[i]);
 }

@Override
public boolean interpret(String info) {
    if(set.contains(info)){
        return true;
    }
    return false;
}
}
  • 非終結(jié)符表達(dá)式 (NonterminalExpression)
 public class AndExpression implements Expression {
 
 private Expression person;
 
 private Expression songName;
 
 public AndExpression(Expression person,Expression songName){
    this.person=person;
    this.songName=songName;
}

@Override
public boolean interpret(String info) {
    String s[]=info.split("的");
    return person.interpret(s[0])&&songName.interpret(s[1]);
}

}
  • 環(huán)境角色
 public class Context {
 
 private String [] persons = {"薛之謙","劉德華","陳奕迅"};
 
 private String [] songNames = {"丑八怪","冰雨","十年"};
 
 private Expression personSong;
 
 public Context(){
    Expression person = new TerminalExpression(persons);
    Expression songName = new TerminalExpression(songNames);
    personSong = new AndExpression(person,songName);
}
  public void findSongs(String info){
    boolean ok = personSong.interpret(info);
    if(ok) System.out.println("正在播放"+info+"吵护,這首歌曲!");
    else System.out.println(info+"表鳍,音樂(lè)播放器暫時(shí)沒(méi)有這首音樂(lè)馅而!");
  }

}
  • 測(cè)試類
 public class Client {
 
 public static void main(String[] args) {
     Context context = new Context();
     context.findSongs("陳奕迅的十年");
     context.findSongs("劉德華的冰雨");
     context.findSongs("薛之謙的丑八怪");
     context.findSongs("周杰倫的青花瓷");
 }

}
  • 測(cè)試結(jié)果
 正在播放陳奕迅的十年,這首歌曲譬圣!
 正在播放劉德華的冰雨瓮恭,這首歌曲!
 正在播放薛之謙的丑八怪厘熟,這首歌曲屯蹦!
 周杰倫的青花瓷维哈,音樂(lè)播放器暫時(shí)沒(méi)有這首音樂(lè)!

六登澜、模式的應(yīng)用場(chǎng)景

??1阔挠、當(dāng)語(yǔ)言的文法較為簡(jiǎn)單,且執(zhí)行效率不是關(guān)鍵問(wèn)題是脑蠕。
??2购撼、當(dāng)問(wèn)題重復(fù)出現(xiàn),且可以用一種簡(jiǎn)單的語(yǔ)言來(lái)進(jìn)行表達(dá)時(shí)谴仙。
??3迂求、當(dāng)一個(gè)語(yǔ)言需要解釋執(zhí)行,并且語(yǔ)言中的句子可以表示為一個(gè)抽象語(yǔ)法樹(shù)的時(shí)候晃跺。

  • 模式的擴(kuò)展

??在項(xiàng)目開(kāi)發(fā)中揩局,如果要對(duì)數(shù)據(jù)表達(dá)式進(jìn)行分析與計(jì)算,無(wú)須再用解釋器模式進(jìn)行設(shè)計(jì)了掀虎,Java 提供了以下強(qiáng)大的數(shù)學(xué)公式解析器:Expression4J凌盯、MESP(Math Expression String Parser)Jep 等,它們可以解釋一些復(fù)雜的文法烹玉,功能強(qiáng)大驰怎,使用簡(jiǎn)單。

??現(xiàn)在以 Jep為例來(lái)介紹該工具包的使用方法春霍。JepJava expression parser 的簡(jiǎn)稱,即 Java表達(dá)式分析器叶眉,它是一個(gè)用來(lái)轉(zhuǎn)換和計(jì)算數(shù)學(xué)表達(dá)式的Java庫(kù)址儒。通過(guò)這個(gè)程序庫(kù),用戶可以以字符串的形式輸入一個(gè)任意的公式衅疙,然后快速地計(jì)算出其結(jié)果莲趣。而且 Jep 支持用戶自定義變量、常量和函數(shù)饱溢,它包括許多常用的數(shù)學(xué)函數(shù)和常量喧伞。

 import com.singularsys.jep.*;
 public class JepDemo{
 public static void main(String[] args) throws JepException{
     Jep jep=new Jep();
     //定義要計(jì)算的數(shù)據(jù)表達(dá)式
     String 存款利息="本金*利率*時(shí)間";
     //給相關(guān)變量賦值
    jep.addVariable("本金",10000);
    jep.addVariable("利率",0.038);
    jep.addVariable("時(shí)間",2);
    jep.parse(存款利息);    //解析表達(dá)式
    Object accrual=jep.evaluate();    //計(jì)算
    System.out.println("存款利息:"+accrual);
}
}
  • 運(yùn)行結(jié)果
        存款利息:760.0
  • 小結(jié)

??解釋器模式使用情況很少,使用時(shí)一定要結(jié)合業(yè)務(wù)進(jìn)行判斷是否符合這種設(shè)計(jì)模式绩郎。

GitHub地址:
??https://github.com/xiaonongOne/interpreter-test/tree/master


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末潘鲫,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子肋杖,更是在濱河造成了極大的恐慌溉仑,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,036評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件状植,死亡現(xiàn)場(chǎng)離奇詭異浊竟,居然都是意外死亡怨喘,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門振定,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)必怜,“玉大人,你說(shuō)我怎么就攤上這事后频∈崆欤” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,411評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵徘郭,是天一觀的道長(zhǎng)靠益。 經(jīng)常有香客問(wèn)我,道長(zhǎng)残揉,這世上最難降的妖魔是什么胧后? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,622評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮抱环,結(jié)果婚禮上壳快,老公的妹妹穿的比我還像新娘。我一直安慰自己镇草,他們只是感情好眶痰,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著梯啤,像睡著了一般竖伯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上因宇,一...
    開(kāi)封第一講書(shū)人閱讀 51,521評(píng)論 1 304
  • 那天七婴,我揣著相機(jī)與錄音,去河邊找鬼察滑。 笑死打厘,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的贺辰。 我是一名探鬼主播户盯,決...
    沈念sama閱讀 40,288評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼饲化!你這毒婦竟也來(lái)了莽鸭?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,200評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤吃靠,失蹤者是張志新(化名)和其女友劉穎蒋川,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體撩笆,經(jīng)...
    沈念sama閱讀 45,644評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡捺球,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評(píng)論 3 336
  • 正文 我和宋清朗相戀三年缸浦,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氮兵。...
    茶點(diǎn)故事閱讀 39,953評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡裂逐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出泣栈,到底是詐尸還是另有隱情卜高,我是刑警寧澤,帶...
    沈念sama閱讀 35,673評(píng)論 5 346
  • 正文 年R本政府宣布南片,位于F島的核電站掺涛,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏疼进。R本人自食惡果不足惜薪缆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望伞广。 院中可真熱鬧拣帽,春花似錦、人聲如沸嚼锄。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,889評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)区丑。三九已至拧粪,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間沧侥,已是汗流浹背可霎。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,011評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留正什,地道東北人啥纸。 一個(gè)月前我還...
    沈念sama閱讀 48,119評(píng)論 3 370
  • 正文 我出身青樓号杏,卻偏偏與公主長(zhǎng)得像婴氮,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子盾致,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評(píng)論 2 355

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

  • 在軟件開(kāi)發(fā)中主经,會(huì)遇到有些問(wèn)題多次重復(fù)出現(xiàn),而且有一定的相似性和規(guī)律性庭惜。如果將它們歸納成一種簡(jiǎn)單的語(yǔ)言罩驻,那么這些問(wèn)題...
    木子教程閱讀 425評(píng)論 0 2
  • 一、概念 1护赊、解釋器模式的動(dòng)機(jī) ? 雖然目前計(jì)算機(jī)編程語(yǔ)言有好幾百種惠遏,但有時(shí)候我們還是希望能用一些簡(jiǎn)單的語(yǔ)言來(lái)實(shí)...
    阿餅six閱讀 922評(píng)論 0 0
  • 解釋器模式(Interpreter) 在軟件開(kāi)發(fā)中砾跃,會(huì)遇到有些問(wèn)題多次重復(fù)出現(xiàn),而且有一定的相似性和規(guī)律性节吮。如果將...
    Acton_zhang閱讀 337評(píng)論 0 2
  • 1.定義 給分析對(duì)象定義一個(gè)語(yǔ)言抽高,并定義該語(yǔ)言的文法表示,再設(shè)計(jì)一個(gè)解析器來(lái)解釋語(yǔ)言中的句子透绩。也就是說(shuō)翘骂,用編譯語(yǔ)言...
    CXY_XZL閱讀 368評(píng)論 0 5
  • 原文傳送門 1 介紹 解釋器模式是類的行為模式。給定一個(gè)語(yǔ)言之后帚豪,解釋器模式可以定義出其文法的一種表示碳竟,并同時(shí)提供...
    dd299閱讀 308評(píng)論 0 1