设计模式(25):解释器模式INTERPRETER

解释器模式

意图

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。


使用场景

当有一个语言需要解释执行, 并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:

  • 该文法简单对于复杂的文法, 文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式, 这样可以节省空间而且还可能节省时间。
  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的, 而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。

结构

解释器模式结构如下
ShowImage

  • AbstractExpression(抽象表达式)

    – 声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。

  • TerminalExpression(终结符表达式)

    – 实现与文法中的终结符相关联的解释操作。

    – 一个句子中的每个终结符需要该类的一个实例。

  • NonterminalExpression(非终结符表达式)

    – 对文法中的每一条规则R ::= R1R2. . . Rn都需要一个NonterminalExpression类。

    – 为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量。

    – 为文法中的非终结符实现解释(Interpret)操作。解释(Interpret)一般要递归地调用表示R1到Rn的那些对象的解释操作。

  • Context(上下文)

    – 包含解释器之外的一些全局信息。

  • Client(客户端)

    – 构建(或被给定) 表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。

    – 调用解释操作。

协作

  • Client构建(或被给定)一个句子, 它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树,然后初始化上下文并调用解释操作。
  • 每一非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。
  • 每一节点的解释操作用上下文来存储和访问解释器的状态。

效果

解释器模式有下列的优点和不足:

  • 易于改变和扩展文法

    – 因为该模式使用类来表示文法规则, 你可使用继承来改变或扩展该文法。已有的表达式可被增量式地改变,而新的表达式可定义为旧表达式的变体。

  • 也易于实现文法

    – 定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个编译器或语法分析程序生成器自动生成。

  • 复杂的文法难以维护

    – 解释器模式为文法中的每一条规则至少定义了一个类(使用BNF定义的文法规则需要更多的类)。因此包含许多规则的文法可能难以管理和维护。可应用其他的设计模式来缓解这一问题。但当文法非常复杂时, 其他的技术如语法分析程序或编译器生成器更为合适。

  • 增加了新的解释表达式的方式

    – 解释器模式使得实现新表达式“计算”变得容易。例如,你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建新的解释表达式的方式, 那么可以考虑使用Visitor模式以避免修改这些代表文法的类。

解释器模式实现(Implement)

案例

现在做个简单的计算器,要求有整数加减的功能,这里可以把加,减,数的操作用解释器模式实现。

代码实现

四则运算表达式

1
2
3
4
/* 四则运算表达式,AbstractExpression in Pattern */
public abstract class ArithmeticExpression {
public abstract int interpret();
}

数字表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
/* 数字表达式, TerminalExpression in Pattern */
public class NumExpression extends ArithmeticExpression {
private int num;
public NumExpression(int num) {
this.num = num;
}
@Override
public int interpret() {
return num;
}
}

将加减操作抽象出一个共同父类操作符表达式

1
2
3
4
5
6
7
8
9
/* 运算符号抽象表达式 */
public abstract class OperatorExpression extends ArithmeticExpression {
protected ArithmeticExpression expression1, expression2;
public OperatorExpression(ArithmeticExpression expression1, ArithmeticExpression expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
}
}

加法表达式

1
2
3
4
5
6
7
8
9
10
11
/* 加法表达式, NonterminalExpression in Pattern */
public class AddExpression extends OperatorExpression {
public AddExpression(ArithmeticExpression expression1, ArithmeticExpression expression2) {
super(expression1, expression2);
}
@Override
public int interpret() {
return expression1.interpret() + expression2.interpret();
}
}

减法表达式

1
2
3
4
5
6
7
8
9
10
11
/* 减法表达式, NonterminalExpression in Pattern */
public class SubExpression extends OperatorExpression {
public SubExpression(ArithmeticExpression expression1, ArithmeticExpression expression2) {
super(expression1, expression2);
}
@Override
public int interpret() {
return expression1.interpret() - expression2.interpret();
}
}

计算器类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class Calculator {
private Stack<ArithmeticExpression> expressionStack = new Stack<>();
public Calculator(String expression) {
ArithmeticExpression expression1, expression2;
String[] elements = expression.split(" ");
for (int i = 0; i < elements.length; i++) {
switch (elements[i].charAt(0)) {
case '+':
expression1 = expressionStack.pop();
expression2 = new NumExpression(Integer.valueOf(elements[++i]));
expressionStack.push(new AddExpression(expression1, expression2));
break;
case '-':
expression1 = expressionStack.pop();
expression2 = new NumExpression(Integer.valueOf(elements[++i]));
expressionStack.push(new SubExpression(expression1, expression2));
break;
default:
expressionStack.push(new NumExpression(Integer.valueOf(elements[i])));
break;
}
}
}
public int calculate() {
return expressionStack.pop().interpret();
}
}

Client测试一下运行结果,单元测试绿灯通过

1
2
3
4
5
6
7
public class CalculatorDemoTest {
@Test
public void calculatorTest() {
Calculator calculator = new Calculator("15 + 20 + 30 - 10 - 11");
assertThat(calculator.calculate()).isEqualTo(44);
}
}

总结

解释器模式易于实现简单的文法,但是对于复杂的文法比较难以维护,而且代码量会大大的膨胀,一般的使用场景较少。

参考代码

------ 本文结束 ------

版权声明


BillyYccc's blog by Billy Yuan is licensed under a Creative Commons BY-NC-SA 4.0 International License.
本文原创于BillyYccc's Blog,转载请注明原作者及出处!