1 四则运算问题
现在有一个四则运算问题需要用代码实现:计算形如a+b-c的值,其中表达式的字母不能为空,并且每个字母都有数字与它对应,最后求出结果。
传统的方法是写一个方法,接收表达式的形式,然后根据用户的输入数值进行解析,最终计算出结果。但是这种方法不利于扩展,即加入新的运算符需要新增对应方法来解析,容易造成程序的混乱。为了更好地解决这个问题,可以考虑使用解释器模式。
2 解释器模式介绍
在编译原理中,一个算术表达式通过词法分析器形成词法单元,然后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。这里的词法分析器和语法分析器都可以称之为解释器。
解释器模式(Interpreter Pattern):给定一个语言(表达式),定义它的文法的一种表示,并形成一个解释器,使用该解释器来解释语言中的句子。
角色分析:
1)Context:环境上下文角色,包含解释器之外额全局信息
2)AbstractExpression:抽象表达式,声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点共享
3)TerminalExpression:终结符表达式,实现与文法中的终结符相关的解释操作
4)NonTerminalExpression:非终结符表达式,为文法中的非终结符实现解释操作
5)Client:调用方,Context和TerminalExpression可以在这里输入

3 代码实现
首先有一个抽象表达式Expression:
// 表达式抽象类,通过Map获取到变量的值
public abstract class Expression {
// 解释公式和数值
public abstract int interpreter(Map<String, Integer> var);
}
终结符表达式继承自Expression,对应到需求中的字母:
public class VarExpression extends Expression {
private String key; // key=a,b,c...
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpreter(Map<String, Integer> var) {
return var.get(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(Map<String, Integer> var) {
return left.interpreter(var) + right.interpreter(var);
}
}
减法运算符表达式类:
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(Map<String, Integer> var) {
return left.interpreter(var) - right.interpreter(var);
}
}
环境上下文类:
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 '+':
left = stack.pop();
right = new VarExpression(String.valueOf(chars[++i]));
stack.push(new AddExpression(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])));
}
}
expression = stack.pop();
}
public int run(Map<String, Integer> var) {
return expression.interpreter(var);
}
}
Client调用:
public class Client {
public static void main(String[] args) {
String expStr = "a+b-c";
Map<String, Integer> var = new HashMap<>();
var.put("a", 2);
var.put("b", 3);
var.put("c", 4);
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + " = " + calculator.run(var));
}
}
输出结果:
运算结果:a+b-c = 1

4 解释器模式的特点
1)当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象的语法树,此时可以考虑使用解释器模式,让程序具有良好的扩展性。
2)解释器模式的使用场景:编译器、运算表达式、正则表达式等。
3)使用解释器可能会带来类膨胀,即Context类可能会非常复杂,另外解释器模式采用递归调用的方式,将会导致调试困难、执行效率低下等问题。