一、模式介绍
解释器模式就是定义多种解释器,为文本或者符号进行解释行为的逻辑意义,比如1+2,需要两种解释器,一种数值解释器告诉我们1和2这种符号代表就是数值1和2,还有一种就是符号解释器,告诉我们+号代表的是前后两个数值做加法运算。因此我们可以看出,解释器模式就像是我们的代码编译器,将固定文法的内容进行解释,构建出计算机能识别的内容进行计算。
核心就是:识别文法(符号)、构建解释。一般包含如下三种角色:
- 抽象表达式,负责定义解释符号或者文本的方法,然后由其子类实现具体的解释逻辑;
- 终结符表达式,实现与终结符有关的解释操作,比如“1+1-2”这个表达式里面,1和2就是终结符表达式;
- 非终结符表达式,实现与非终结符有关的解释操作,比如“1+1-2”这个表达式里面,+和-符号就是终结符表达式;
- 上下文环境类,用来对文本或者符号进行分类,然后调用各种解释器进行解释;
我们以一个计算整数加减法的计算器为例:
/**
* 抽象表达式,定义解释符号的行为
*/
public abstract class Expression {
public abstract int interpreter(HashMap<String,Integer> var);
}
/**
* 数值表达式,对自然数符号进行解释
*/
public class VarExpression extends Expression{
private String name;
public VarExpression(String name){
this.name = name;
}
/**
* 自然数符号的解释就是返回当前name对应的数值
* @param var
* @return
*/
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(name);
}
}
/**
* 运算符表达式,对运算符进行解释
*/
public class SymbolExpression extends Expression{
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right){
this.left = left;
this.right = right;
}
/**
* 运算符分为加号运算符和减号运算符,具体解释逻辑需要在子类中实现
* @param var
* @return
*/
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}
/**
* 加号运算符表达式,对加号运算符进行解释
*/
public class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}
/**
* 加号运算符的解释就是两数相加的结果
* @param var
* @return
*/
@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);
}
/**
* 减号运算符的解释就是两数相减的结果
* @param var
* @return
*/
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
public class Calculator {
private Expression expression;
public Calculator(String expressionStrings){
// 使用栈这种数据结构来处理加减运算
Stack<Expression> stack = new Stack<>();
char[] chars = expressionStrings.toCharArray();
Expression left = null;
Expression right = null;
// 1+1-2这样的表达式在如下循环后,stack中只会有有一个元素
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:
// 数值符号,name就指定为当前数值,再入栈,等待和后面的表达式进行递归计算
stack.push(new VarExpression(String.valueOf(chars[i])));
break;
}
}
// 获取stack中最终形成的唯一的元素
this.expression = stack.pop();
}
/**
* 对当前表达式进行递归解释
* @param var
* @return
*/
public int doCalculate(HashMap<String, Integer> var){
return this.expression.interpreter(var);
}
}
@Slf4j
public class Main {
public static void main(String[] args) {
String expressionStrins = "a+b-c";
HashMap<String,Integer> vars = new HashMap<>();
vars.put("a",1);
vars.put("b",1);
vars.put("c",2);
Calculator casio = new Calculator(expressionStrins);
Integer result = casio.doCalculate(vars);
log.info("计算结果为:{}", result);
}
}
二、使用场景
- JDK中Pattern对正则表达式的编译和解析;
- Spring中ExpressionParser接口的使用;
三、模式总结
3.1 优点
- 符号语法是由各个解释器类负责解释的,当符号表达的含义需要变更时,只需要对应的解释类即可,当需要增加新的符号时,也只需要增加新的解释器类即可,符合开闭原则;
- 提供给我们一种新的用来解释表达式的方法;
- 比较适合解释语法规则简单的场景;
3.2 缺点
- 每个新的符号语法都要新增一个解释器,当符号种类比较多的时候,就会产生大量的解释类,增加系统的维护难度;
- 解释器模式使用到了递归的思想,当表达式较长,递归深度就会很深,比较占用资源,解释效率就会下降,且很难调试;
网友评论