本文實(shí)例代碼:https://github.com/JamesZBL/java_design_patterns
解釋器(Interpreter)模式提供了校驗(yàn)語言的語法或表達(dá)式的途徑,它屬于行為型模式的一種。這種模式通常會(huì)提供一個(gè)表達(dá)式接口,通過這個(gè)接口可以解釋對(duì)應(yīng)特定環(huán)境的上下文。
解釋器模式在日常開發(fā)的過程中不是很常用,但它在 SQL 解析、符號(hào)處理引擎、編譯程序等場景中使用非常廣泛。
實(shí)例
給定一個(gè)語言,解釋器模式可以定義出其文法的一種表示,并定義一個(gè)解釋器,該解釋器使用該表示來解釋語言中的句子。舉個(gè)簡單的例子,在某種計(jì)算器中輸入 5+1-3*2,每輸入一個(gè)字符,屏幕上都會(huì)顯示當(dāng)前的結(jié)果,和這種計(jì)算器不同的是另外一種,即一次性輸入 5+1-3*2 然后點(diǎn)擊 =,直接得出最終結(jié)果,后面這種計(jì)算器就用到了解釋器。
我們輸入的這一系列符號(hào)可以用二叉樹的形式來表示,比如 5+1-3*2:

這個(gè)二叉樹就是一個(gè)簡單的語法樹,在一般的計(jì)算機(jī)程序設(shè)計(jì)語言的編譯過程中,通常都包含類似的語法樹生成的過程。
圖中的 5、 1、 3、 2 都叫做 終結(jié)符表達(dá)式,所謂終結(jié)符就是本身不能再推導(dǎo)出其他符號(hào)了,圖中的 +、 -、 * 這些四則運(yùn)算符號(hào)就是 非終結(jié)符表達(dá)式 了,因?yàn)榭梢杂蛇@些符號(hào)分別展開,形成各自的子表達(dá)式。對(duì)于四則運(yùn)算表達(dá)式,解析的結(jié)果就是運(yùn)算結(jié)果,所以運(yùn)算符號(hào)可以抽象出一個(gè)接口,包含一個(gè)返回值為整數(shù)(假設(shè)只有整數(shù)參與運(yùn)算)的 interpret() 方法。
Expression.java
ublic abstract class Expression {
public abstract int interpret() throws Exception;
@Override
public abstract String toString();
}
由于加減乘除解釋的出來的運(yùn)算結(jié)果顯然是不同的,所以分別實(shí)現(xiàn)這個(gè)接口形成四個(gè)類,四則運(yùn)算符號(hào)需要左右兩個(gè)操作數(shù)才能進(jìn)行解釋運(yùn)算,所以每個(gè)運(yùn)算符都持有兩個(gè)符號(hào)的引用,分別作為其左運(yùn)算數(shù)和右運(yùn)算數(shù):
加號(hào),PlusExpression.java:
public class PlusExpression extends Expression {
private Expression expressionLeft;
private Expression expressionRight;
public PlusExpression(Expression expressionLeft, Expression expressionRight) {
this.expressionLeft = expressionLeft;
this.expressionRight = expressionRight;
}
@Override
public int interpret() throws Exception {
return expressionLeft.interpret() + expressionRight.interpret();
}
@Override
public String toString() {
return "+";
}
}
減號(hào),MinusExpression.java:
public class MinusExpression extends Expression {
private Expression expressionLeft;
private Expression expressionRight;
public MinusExpression(Expression expressionLeft, Expression expressionRight) {
this.expressionLeft = expressionLeft;
this.expressionRight = expressionRight;
}
@Override
public int interpret() throws Exception {
return expressionLeft.interpret() - expressionRight.interpret();
}
@Override
public String toString() {
return "-";
}
}
乘號(hào),MultipleExpression.java:
public class MultipleExpression extends Expression {
private Expression expressionLeft;
private Expression expressionRight;
public MultipleExpression(Expression expressionLeft, Expression expressionRight) {
this.expressionLeft = expressionLeft;
this.expressionRight = expressionRight;
}
@Override
public int interpret() throws Exception {
return expressionLeft.interpret() * expressionRight.interpret();
}
@Override
public String toString() {
return "*";
}
}
除號(hào),DivisionExpression.java:
public class DivisionExpression extends Expression {
private Expression expressionLeft;
private Expression expressionRight;
public DivisionExpression(Expression expressionLeft, Expression expressionRight) {
this.expressionLeft = expressionLeft;
this.expressionRight = expressionRight;
}
@Override
public int interpret() throws Exception{
return expressionLeft.interpret() / expressionRight.interpret();
}
@Override
public String toString() {
return "/";
}
}
對(duì)于一個(gè)四則運(yùn)算的算式,除了這四個(gè)四則運(yùn)算符號(hào),就是數(shù)字了,所以將數(shù)字抽象出一個(gè)數(shù)字符號(hào)類:
NumberExpression.java
public class NumberExpression extends Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
public NumberExpression(String numberString) {
this.number = Integer.parseInt(numberString);
}
@Override
public int interpret() {
return number;
}
@Override
public String toString() {
return "數(shù)字";
}
}
將上文中的算式用二叉樹的形式表示,按順序展開為一個(gè)字符序列,即 - + * 5 1 3 2,現(xiàn)在模仿計(jì)算器對(duì)其進(jìn)行解釋,這里用到了一點(diǎn)數(shù)據(jù)結(jié)構(gòu)的知識(shí),遍歷二叉樹通常采用 堆棧 (Stack) 結(jié)構(gòu)來實(shí)現(xiàn):
App.java
public class Application {
private static final Logger LOGGER = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
try {
String tokenString = "- + * 5 1 3 2";
Stack<Expression> stack = new Stack<>();
String[] stringList = tokenString.split(" ");
for (String s : stringList) {
if (isOperator(s)) {
Expression expressionRight = stack.pop();
Expression expressionLeft = stack.pop();
LOGGER.info("左操作數(shù):{},右操作數(shù):{}", expressionLeft.interpret(), expressionRight.interpret());
Expression expression = getExpressionInstance(s, expressionLeft, expressionRight);
LOGGER.info("操作符:{}", expression);
Expression result;
if (expression != null) {
result = new NumberExpression(expression.interpret());
LOGGER.info("運(yùn)算結(jié)果為:{}", result.interpret());
stack.push(result);
}
} else {
NumberExpression expression = new NumberExpression(s);
stack.push(expression);
LOGGER.info("數(shù)字入棧:{}", expression.interpret());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 判斷字符串是否為四則運(yùn)算的操作符
*
* @param s 待判斷的字符串
*
* @return 是否為操作符
*/
public static boolean isOperator(String s) {
return s.equals("+") || s.equals("-") || s.equals("*") || s.equals("/");
}
/**
* 根據(jù)字符串生成四則運(yùn)算表達(dá)式
*
* @param s 字符串
* @param expressionLeft 左表達(dá)式
* @param expressionRight 右表達(dá)式
*
* @return 四則運(yùn)算表達(dá)式
*/
public static Expression getExpressionInstance(String s, Expression expressionLeft, Expression expressionRight) {
if (isOperator(s)) {
switch (s) {
case "+": {
return new PlusExpression(expressionLeft, expressionRight);
}
case "-": {
return new MinusExpression(expressionLeft, expressionRight);
}
case "*": {
return new MultipleExpression(expressionLeft, expressionRight);
}
case "/": {
return new DivisionExpression(expressionLeft, expressionRight);
}
}
}
return null;
}
}
總結(jié)
解釋器主要運(yùn)用方式就是解釋語法樹的節(jié)點(diǎn),它將語法解釋規(guī)則和實(shí)現(xiàn)結(jié)構(gòu),將復(fù)雜的解釋工作指派到不同的解釋器對(duì)象中,是什么語法就由什么解釋器來解釋。對(duì)于一棵生成好的語法樹,通常由根節(jié)點(diǎn)開始解釋,不斷遞歸,依次選擇適合子節(jié)點(diǎn)的解釋器來進(jìn)行解釋。
解釋器的應(yīng)用場景:
當(dāng)一個(gè)語言需要解釋執(zhí)行,并可以將該語言中的句子表示為一個(gè)抽象語法樹的時(shí)候,例如 XML 文件或正則表達(dá)式
一些重復(fù)出現(xiàn)的問題可以用一種簡單的語言來進(jìn)行表達(dá)
一個(gè)語言的文法較為簡單,比如四則運(yùn)算
當(dāng)執(zhí)行效率不是關(guān)鍵和主要關(guān)心的問題時(shí)可考慮解釋器模式,因?yàn)榇罅渴褂眠f歸循環(huán)調(diào)用,隨著語法的復(fù)雜程度加劇,解釋器的執(zhí)行效率會(huì)非常低
個(gè)人博客同步更新,獲取更多技術(shù)分享請(qǐng)關(guān)注:鄭保樂的博客