引子
解釋器模式應(yīng)該是一個(gè)很少會(huì)用到的模式,也不太好理解,用例子的方式來(lái)講解吧。
定義
解釋器模式是類的行為模式。給定一個(gè)語(yǔ)言之后,解釋器模式可以定義出其文法的一種表示,并同時(shí)提供一個(gè)解釋器。客戶端可以使用這個(gè)解釋器來(lái)解釋這個(gè)語(yǔ)言中的句子。
組成結(jié)構(gòu)
一個(gè)解釋器模式中包含的四種角色
- 抽象解釋器:聲明一個(gè)所有具體表達(dá)式都要實(shí)現(xiàn)的抽象接口(或者抽象類),接口中主要是一個(gè)interpret()方法,稱為解釋操作。具體解釋任務(wù)由它的各個(gè)實(shí)現(xiàn)類來(lái)完成,具體的解釋器分別由終結(jié)符解釋器和非終結(jié)符解釋器完成。
- 終結(jié)符表達(dá)式:實(shí)現(xiàn)與文法中的元素相關(guān)聯(lián)的解釋操作,通常一個(gè)解釋器模式中只有一個(gè)終結(jié)符表達(dá)式,但有多個(gè)實(shí)例,對(duì)應(yīng)不同的終結(jié)符。終結(jié)符一半是文法中的運(yùn)算單元,比如有一個(gè)簡(jiǎn)單的公式R=R1+R2,在里面R1和R2就是終結(jié)符,對(duì)應(yīng)的解析R1和R2的解釋器就是終結(jié)符表達(dá)式。
- 非終結(jié)符表達(dá)式:文法中的每條規(guī)則對(duì)應(yīng)于一個(gè)非終結(jié)符表達(dá)式,非終結(jié)符表達(dá)式一般是文法中的運(yùn)算符或者其他關(guān)鍵字,比如公式R=R1+R2中,+就是非終結(jié)符,解析+的解釋器就是一個(gè)非終結(jié)符表達(dá)式。非終結(jié)符表達(dá)式根據(jù)邏輯的復(fù)雜程度而增加,原則上每個(gè)文法規(guī)則都對(duì)應(yīng)一個(gè)非終結(jié)符表達(dá)式。
- 環(huán)境角色:這個(gè)角色的任務(wù)一般是用來(lái)存放文法中各個(gè)終結(jié)符所對(duì)應(yīng)的具體值,比如R=R1+R2,我們給R1賦值100,給R2賦值200。這些信息需要存放到環(huán)境角色中,很多情況下我們使用Map來(lái)充當(dāng)環(huán)境角色就足夠了。
類圖
簡(jiǎn)單類圖如下所示
例子
我們需要一個(gè)能夠解釋加法和減法的解釋器,那么最好的方式就是用解釋器模式來(lái)實(shí)現(xiàn)。
先來(lái)看看抽象表達(dá)式
public abstract class Expression {
/**
* 以環(huán)境為準(zhǔn),本方法解釋給定的任何一個(gè)表達(dá)式
*/
public abstract int interpret(Context ctx);
/**
* 檢驗(yàn)兩個(gè)表達(dá)式在結(jié)構(gòu)上是否相同
*/
public abstract boolean equals(Object obj);
/**
* 返回表達(dá)式的hash code
*/
public abstract int hashCode();
/**
* 將表達(dá)式轉(zhuǎn)換成字符串
*/
public abstract String toString();
}
因?yàn)樾枰忉尲臃ê蜏p法,因此實(shí)現(xiàn)類也有Plus和Minus兩個(gè)非終結(jié)符表達(dá)式
public class Plus extends Expression {
private Expression left,right;
public Plus(Expression left , Expression right){
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Plus)
{
return left.equals(((Plus)obj).left) &&
right.equals(((Plus)obj).right);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public int interpret(Context ctx) {
return left.interpret(ctx) + right.interpret(ctx);
}
@Override
public String toString() {
return "(" + left.toString() + " + " + right.toString() + ")";
}
}
public class Minus extends Expression {
private Expression left, right;
public Minus(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public boolean equals(Object obj) {
if (obj != null && obj instanceof Minus) {
return left.equals(((Minus) obj).left) && right.equals(((Minus) obj).right);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public int interpret(Context ctx) {
return left.interpret(ctx) - right.interpret(ctx);
}
@Override
public String toString() {
return "(" + left.toString() + " - " + right.toString() + ")";
}
}
除此之外我們還需要變量與常量?jī)蓚€(gè)終結(jié)符表達(dá)式
public class Constant extends Expression{
private int value;
public Constant(int value){
this.value = value;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Constant){
return this.value == ((Constant)obj).value;
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public int interpret(Context ctx) {
return value;
}
@Override
public String toString() {
return new Integer(value).toString();
}
}
public class Variable extends Expression {
private String name;
public Variable(String name){
this.name = name;
}
@Override
public boolean equals(Object obj) {
if(obj != null && obj instanceof Variable)
{
return this.name.equals(
((Variable)obj).name);
}
return false;
}
@Override
public int hashCode() {
return this.toString().hashCode();
}
@Override
public String toString() {
return name;
}
@Override
public int interpret(Context ctx) {
return ctx.lookup(this);
}
}
環(huán)境角色基本可以存放文法中各個(gè)終結(jié)符所對(duì)應(yīng)的具體值,下面的類就是用一個(gè)map來(lái)負(fù)責(zé)
public class Context {
private Map<Variable,Integer> map = new HashMap<Variable,Integer>();
public void assign(Variable var , int value){
map.put(var, new Integer(value));
}
public int lookup(Variable var) throws IllegalArgumentException{
Integer value = map.get(var);
if(value == null){
throw new IllegalArgumentException();
}
return value.intValue();
}
}
Client客戶端的代碼
public class Client {
public static void main(String[] args) {
Context ctx = new Context();
Variable x = new Variable("x");
Variable y = new Variable("y");
Constant c = new Constant(1);
ctx.assign(x, 2);
ctx.assign(y, 3);
Expression exp = new Plus(new Plus(c,x) , new Minus(y,x));
System.out.println(exp.toString() + "=" + exp.interpret(ctx));
}
}
輸出結(jié)果如下
((1 + x) + (y - x))=4
優(yōu)缺點(diǎn)
解釋器是一個(gè)簡(jiǎn)單的語(yǔ)法分析工具,它最顯著的優(yōu)點(diǎn)就是擴(kuò)展性,修改語(yǔ)法規(guī)則只需要修改相應(yīng)的非終結(jié)符就可以了,若擴(kuò)展語(yǔ)法,只需要增加非終結(jié)符類就可以了。比如我們現(xiàn)在需要修改Plus的含義,那么只需要修改Plus類即可,如果要增加乘法操作,那么也只需要增加一個(gè)Multiple類就行。
但是,解釋器模式會(huì)引起類的膨脹,每個(gè)語(yǔ)法都需要產(chǎn)生一個(gè)非終結(jié)符表達(dá)式,語(yǔ)法規(guī)則比較復(fù)雜時(shí),就可能產(chǎn)生大量的類文件,為維護(hù)帶來(lái)非常多的麻煩。同時(shí),從例子我們也可以看到,整個(gè)表達(dá)式的運(yùn)算過(guò)程采用了遞歸調(diào)用方法,每個(gè)非終結(jié)符表達(dá)式只關(guān)心與自己相關(guān)的表達(dá)式,每個(gè)表達(dá)式需要知道最終的結(jié)果,必須通過(guò)遞歸方式,無(wú)論是面向?qū)ο蟮恼Z(yǔ)言還是面向過(guò)程的語(yǔ)言,遞歸都是一個(gè)不推薦的方式。由于使用了大量的循環(huán)和遞歸,效率是一個(gè)不容忽視的問(wèn)題。特別是用于解釋一個(gè)解析復(fù)雜、冗長(zhǎng)的語(yǔ)法時(shí),效率是難以忍受的。
使用場(chǎng)景
一些重復(fù)發(fā)生的事情包含固定的一系列操作類型,比較適合用解釋器模式來(lái)實(shí)現(xiàn)。比如加減乘除四則運(yùn)算,但是公式每次都不同,比如可配置,有時(shí)是a + b - c x d,有時(shí)是a x b + c - d,等等等等個(gè),公式千變?nèi)f化,但是都是由加減乘除四個(gè)非終結(jié)符來(lái)連接的,這時(shí)我們就可以使用解釋器模式。