??解釋器模式(
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)介紹該工具包的使用方法春霍。Jep
是 Java 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