1.为什么要使用解释器模式
解释器模式的应用场景为,当一个系统的需要运算的公式过多时,要为每一个公式实现一个接口,那么接口数量必然暴增,但是使用解释器模式的话,可以让代码去解释每一个公式,并实现自动计算,这样,我们就不需要为每一个公式进行开发接口。有点类似于人工智能的感觉。学习这个模式的时候,让我想起了一门课程,叫编译原理。回想此课程,令人痛不欲生。因为其语法树解析的概念,正是解释器模式的核心概念。
2.先看类图
以加减运算为例子。比如说我的系统有A+B的公式,也有A-B的公式,还有A+B-C的公式,还有其它公式,通常来说,我们会为每一种公式开发一个接口,根据业务逻辑去选择哪个接口调用,这种方式在公式少的时候可以去进行使用,但是公式一旦多起来,就很麻烦。但是解释器模式不同,你只要把公式给我,并把A,B,C的值给我,那么我就可以帮你计算出来结果。上面所说的Calculator相当于一个解释器,我们要把公式给它。Expression就相当于一个表达式,其中有VarExpression,表示值表达式(在性质上来说是终结符表达式),就是ABC;SymbolExpression是运算符表达式(在性质上说是非终结符表达式),Add,Sub就是它的具体实现,来看代码。
3.实现
Expression相关类
/**
* 抽象表达式
*/
public abstract class Expression {
//解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
//这里的HashMap是用来存放表达式中的数值
//比如 A + B,那么这么Map里面就是放 put(“A”,10) put(“B”,20)
public abstract int interpreter(HashMap<String,Integer> var);
}
/**
* 数值表达式
*/
public class VarExpression extends Expression{
//表达式的Key,比如A,B
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
/**
* 运算符表达式
*/
public abstract class SymbolExpression extends Expression{
/**
* 二叉树结构
* 因为一个运算符两边肯定都是数值表达式
*/
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
/**
* 加法表达式
*/
public class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}
/**
* 返回左边的表达式 + 右边的表达式
*/
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
/**
* 减法表达式
*/
public class SubExpression extends SymbolExpression{
public SubExpression(Expression left, Expression right) {
super(left, right);
}
/**
* 返回左边的表达式 - 右边的表达式
*/
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
Calculator类
/**
* 解释器
*/
public class Calculator {
/**
* 该成员变量只是保存运算结果
*/
private Expression expression;
public Calculator(String expStr) {
/**
* 利用栈的结构
*/
Stack<Expression> stack = new Stack<>();
char[] chars = expStr.toCharArray();
Expression left = null;
Expression right = null;
for (int i=0; i<chars.length; i++){
switch (chars[i]) {
case '+':
//如果是 + ,则构造新的表达式 A+B,并且++i,跳过下一个数字的循环
left = stack.pop();//取得+号左边的数字
right = new VarExpression(String.valueOf(chars[++i]));//取得+号右边的数字,并把下标+1,跳过下一次循环
stack.push(new AddExpression(left, right));//构造出新的 left+right 表达式,并入栈
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(chars[++i]));
stack.push(new SubExpression(left, right));
break;
default:
stack.push(new VarExpression(String.valueOf(chars[i])));
}
}
//运算结果
this.expression = stack.pop();
}
public int run(HashMap<String,Integer> var){
//执行递归解析
return this.expression.interpreter(var);
}
}
main方法
public class Main {
public static void main(String[] args) {
String expStr = "a+b-a+c";
HashMap<String, Integer> values = new HashMap<>();
values.put("a", 10);
values.put("b", 20);
values.put("c", 30);
Calculator calculator = new Calculator(expStr);
int run = calculator.run(values);
//显示结果
for (Character c : expStr.toCharArray()){
System.out.print(values.containsKey(c.toString()) ? values.get(c.toString()) : c.toString());
}
System.out.println(" = " +run);
}
}
//输出结果
10+20-10+30 = 50
4.总结
以上就是解释器模式。实现起来非常的麻烦。而且是只有加减的情况下,但是使用起来非常简单。如果在此基础上再加上乘除,那么判断就不是这么简单了,还要考虑计算的优先级。可见一个想要实现编译器的语法解析树得有多复杂。不过幸运的是,现在第三方已经提供了一些关于运算的解释器模式,Expression4J、MESP(Math Expression String Parser)、Jep等。大家可以自行使用