解释器模式

定义

解释器模式是给定一门语言,定义它的语法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子,是一种按照规定语法进行解析的方案,属于行为型模式,在现在项目中使用较少。

如编译器可将源码编译解释为机器码,让CPU能进行识别并运行。解释噐模式的作用其实与编译器一样,都是将一些固定的语法进行解释,构建出一个解释句子的解释器。简单理解解释器是一个简单语法分析工具,它可以识别句子语义分离终结符号非终结符号,提取出需要的信息,能针对不同的信息做出相应的处理,其核心思想是识别语法构建解释

实现

解释器模式主要包含四种角色:

  • 抽象表达式(Expression):负责定义ー个解释方法Interpret,交由具体子类进行具体解释
  • 终结符表达式(TerminalExpression):实现语法中与终结符有关的解释操作,语法中的每个终结符都有一个具体终结表达式与之相对应,如公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符
  • 非终结符表达式(NonTerminalExpression):实现语法中与非终结符有关的解释操作,语法中的每条规则都对应于ー个非终结符表达式,非终结符表达式一般是语法中的运算符其他关键字,如公式R=R1+R2中,+就是非终结符,解析+的解释器就是一个非终结符
    表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个语法规则都对应一个非终结符表达式
  • 上下文环境类(Context):包含解释器之外的全局信息,其任务一般是用来存放语法中各个终结符所对应的具体值,如R=R1+R2,给R1赋值100,给R2赋值200,这些信息需要存放到环境中
抽象表达式
1
2
3
4
public abstract class Expression {
// 解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
public abstract int interpreter(Map<String, Integer> var);
}
终结符表达式
1
2
3
4
5
6
7
8
9
10
public class VarExpression extends Expression {
private String key;
public VarExpression(String key) {
this.key = key;
}
// 从map中取之
public int interpreter(Map<String, Integer> var) {
return var.get(this.key);
}
}
非终结符表达式
1
2
3
4
5
6
7
8
9
public abstract class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
// 所有的解析公式都应只关心自己左右两个表达式的结果
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
1
2
3
4
5
6
7
8
9
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
// 左右两个表达式相减
public int interpreter(Map<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
1
2
3
4
5
6
7
8
9
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
// 把左右两个表达式运算的结果加起来
public int interpreter(Map<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
上下文环境类
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
32
33
34
35
36
37
public class Calculator {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr) {
// 定义一个栈,安排运算的先后顺序
Stack<Expression> stack = new Stack<>();
// 表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
// 运算
Expression left = null;
Expression right = null;
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+': //加法
//加法结果放到栈中
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default: // 公式中的变量
stack.push(new VarExpression(String.valueOf(charArray[i])));
}
}
// 把运算结果抛出来
this.expression = stack.pop();
}
// 开始运算
public int run(Map<String, Integer> var) {
return this.expression.interpreter(var);
}
}
Client调用
1
2
3
4
5
6
7
8
9
String expStr = "a+b-c+d";
//赋值
Map<String, Integer> var = new HashMap<>();
var.put("a", 6);
var.put("b", 3);
var.put("c", 8);
var.put("d", 5);
Calculator cal = new Calculator(expStr);
System.out.println("运算结果为: " + expStr + "=" + cal.run(var));

优点

  • 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可
  • 増加了新的解释表达式的方式
  • 易于实现语法:解释噐模式对应的语法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式

缺点

  • 语法规则较复杂时会引起类膨胀,解释器模式每个语法都要产生一个非终结符表达式,当语法规则比较复杂时,就会产生大量的解释类,增加系统维护困难
  • 执行效率比较低:解释器模式采用递归调用方法,每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,因此完整表达式的最终结果是通过从后往前递归调用的方式获取得到。当完整表达式层级较深时解释效率下降,且出错时调试困难

应用

若存在一种特定类型的问题,该类型问题涉及多个不同实例,但是具备固定语法描述,则可使用解释噐模式对该类型问题进行解释,分离出需要的信息,根据获取的信息做出相应的处理。即对于ー些固定语法构建一个解释句子的解释器

  • 一些重复出现的问题可用一种简单的语言来进行表达
  • 简单语法需要解释的场景

尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。 在项目中可以使用ShellJRubyGroovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。

解释器模式在实际的系统开发中使用得非常少, 因为它会引起效率性能维护等问题, 一般在大中型的框架型项目能够找到它的身影, 如一些数据分析工具报表设计工具科学计算工具等, 若确实遇到一种特定类型的问题发生的频率足够高的情况, 准备使用
解释器模式时, 可以考虑使用Expression4JMESPMath Expression String ParserJep等开源的解析工具包。

扩展

JDK源码中的Pattern正则表达式的编译和解析

SpringSpelExpressionParser和谷歌的aviator都是表达式引擎,应用了解释器模式。