案例
计算a+b-c的值,先输入表达式的形式,比如a+b-c,要求表达式的字母不能重复,再分别输入a,b,c的值,最后求出结果。
传统方案
编写一个计算方法,接收表达式的形式,然后根据用户输入的数值进行解析得到结果(将所有的计算细节放在一个方法里)
传统方案带来的问题
如果加入新的运算符,比如*/等等,需要对计算方法进行修改,违背了开闭原则,不利于扩展;另外将所有的计算细节都放到一个方法里会使得该方法非常臃肿,程序结构混乱,程序难以维护
使用解释器模式
解决方案:表达式->解释器(可以有多种)->结果
几个重要的角色:Expression抽象表达式类,声明了一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享;VarExpression终结符表达式类,实现与文法中的终结符相关的解释操作;SymbolExpression非终结符表达式类,为文法中的非终结符实现解释操作。AddExpression实现对加的解释操作,SubExpression实现对减的解释操作
/**
*
* @ClassName InterpreterPattern
* @Description 解释器模式
* @author fuling
* @date 2020年11月3日 下午9:11:20
*/
public class InterpreterPattern {
public static void main(String[] args) {
String exp = "a+b-c";
Map<String, Integer> var = new HashMap<>();
var.put("a", 1);
var.put("b", 4);
var.put("c", 2);
Calculator calculator = new Calculator(exp);
System.out.println(calculator.run(var));
}
}
class Calculator{
Expression expression;
Calculator(String exp){
Stack<Expression> stack = new Stack();
char[] charArray = exp.toCharArray();
Expression left;
Expression right;
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])));
break;
}
}
this.expression = stack.pop();
}
int run(Map<String, Integer> var) {
return this.expression.interpreter(var);
}
}
/**
*
* @ClassName Expression
* @Description 抽象表达式类
* @author fuling
* @date 2020年11月3日 下午9:17:56
*/
abstract class Expression{
/**
*
* @Title interpreter
* @Description 描述这个方法的作用
* @param @param var 存放终结符和数值的映射,比如{a=>1,b=>3}
* @param @return 参数说明
* @return 返回解释器解析后的结果
* @throws
*/
abstract int interpreter(Map<String, Integer> var);
}
/**
*
* @ClassName VarExpression
* @Description 终结符表达式
* @author fuling
* @date 2020年11月3日 下午9:27:15
*/
class VarExpression extends Expression{
String key;
VarExpression(String key){
this.key = key;
}
/**
*
* @Title interpreter
* @Description 终结符的解释方法:从键值对映射中取出值
* @param @param var
* @param @return
* @see Expression#interpreter(java.util.Map)
*/
@Override
int interpreter(Map<String, Integer> var) {
return var.get(key);
}
}
/**
*
* @ClassName SymbolExpression
* @Description 非终结符表达式
* @author fuling
* @date 2020年11月3日 下午9:30:18
*/
abstract class SymbolExpression extends Expression{
Expression left;//左操作表达式
Expression right;//右操作表达式
SymbolExpression(Expression left, Expression right){
this.left = left;
this.right = right;
}
@Override
abstract int interpreter(Map<String, Integer> var);
}
/**
*
* @ClassName SubExpression
* @Description “减”表达式
* @author fuling
* @date 2020年11月3日 下午9:30:33
*/
class SubExpression extends SymbolExpression{
SubExpression(Expression left, Expression right) {
super(left, right);
}
/**
*
* @Title interpreter
* @Description 非终结符的解释方法:对两边的操作表达式执行减的操作
* @param @param var
* @param @return
* @see SymbolExpression#interpreter(java.util.Map)
*/
@Override
int interpreter(Map<String, Integer> var) {
return this.left.interpreter(var) - this.right.interpreter(var);
}
}
/**
*
* @ClassName AddExpression
* @Description “加”表达式
* @author fuling
* @date 2020年11月3日 下午9:30:58
*/
class AddExpression extends SymbolExpression{
AddExpression(Expression left, Expression right) {
super(left, right);
}
/**
*
* @Title interpreter
* @Description 非终结符的解释方法:对两边的操作表达式执行加的操作
* @param @param var
* @param @return
* @see SymbolExpression#interpreter(java.util.Map)
*/
@Override
int interpreter(Map<String, Integer> var) {
return this.left.interpreter(var) + this.right.interpreter(var);
}
}
使用了解释器模式后,程序具有更好的扩展性,当需求增加一种新的运算符时,只需要添加一个新解释器即可
解释器模式小结
- 解释器模式,是指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有更好的扩展性
- 使用解释器模式可能带来的问题:引起类膨胀;由于解释器模式采用递归调用方法,这将会导致调试非常复杂,效率可能降低
- 应用场景:编译器,运算表达式计算,正则表达式,机器人等
解释器模式在spring框架中的使用
SpelExpressionParser使用到了解释器模式,该类实现了ExpressionParser接口
public interface ExpressionParser {
/**
* Parse the expression string and return an Expression object you can use for repeated evaluation.
* <p>Some examples:
* <pre class="code">
* 3 + 4
* name.firstName
* </pre>
* @param expressionString the raw expression string to parse
* @return an evaluator for the parsed expression
* @throws ParseException an exception occurred during parsing
*/
Expression parseExpression(String expressionString) throws ParseException;
/**
* Parse the expression string and return an Expression object you can use for repeated evaluation.
* <p>Some examples:
* <pre class="code">
* 3 + 4
* name.firstName
* </pre>
* @param expressionString the raw expression string to parse
* @param context a context for influencing this expression parsing routine (optional)
* @return an evaluator for the parsed expression
* @throws ParseException an exception occurred during parsing
*/
Expression parseExpression(String expressionString, ParserContext context) throws ParseException;
}
该接口的parseExpression方法可以返回Expression实例,也就是说不同的ExpressionParser 实现类返回不同的Expression