20.java设计模式之解释器模式
基本需求
- 实现四则运算,如计算a+b-c+d的值
- 先输入表达式的形式,如a+b-c+d,要求表达式正确
- 再分别输出a,b,c,d的值
- 最后求出结果
传统方案
- 编写一个方法,接收表达式的形式,根据用户输入的数值进行解析,得到结果
- 如果加入新的运算符,比如*/(等等,不利于扩展,另外让一个方法解析会造成程序结构的混乱
- 使用解释器模式,表达式 -> 解释器(多种) -> 结果
基本介绍
-
在编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树,这里的词法分析器和语法分析器都可以看做是解释器
-
解释器模式(Interpreter)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在SQL解析、符号处理引擎等
-
解释器模式指给定一个语言(表达式),定义它的文法的一种表示,并定义一个解释器,使用该解释器来解释语言中的句子(表达式)
-
应用场景
- 应用可以将一个需要解释执行的语言中的句子表示为一个抽象语法树
- 一些重复出现的问题可以用一种简单的语言来表达
- 一个简单语法需要解释的场景
- 比如编译器、运算表达式计算、正则表达式等
-
UML类图(原理)
-
-
说明
- Context:是环境角色,含有解释器之外的全局信息
- AbstractExpression:抽象表达式,声明一个抽象的解释器操作,这个方法为抽象语法树中所有的节点所共享
- TerminalExpression:终结符表达式,实现与文法中的终结符相关的解释操作(相当于递归的出口)
- NonTerminalExpression:非终结符表达式,为文法中的非终结符实现解释操作(需要聚合AbstractExpression,可能是TerminalExpression 或 NonTerminalExpression,最终构成一个递归结构)
- Context 和 TerminalExpression 信息通过Client输入即可
-
-
UML类图(案例)
-
构建解释器流程示意
-
代码实现
-
// 抽象表达式类
public abstract class AbstractExpression {
// 声明一个解释操作 map的含义是作为终结者表达式获取值的方式 一般有Context提供,也就是递归的出口
// 本次案例中 map的形式为{a=1,b=2,c=3...} key 为 表达式中字母 value为每个字母具体的值
public abstract int interpreter (Map<String, Integer> map);
}
-
// 变量表达式 此处作为了终结表达式 提供递归的出口
public class VarExpression extends AbstractExpression {
// 此处为key为表达式的字母
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpreter(Map<String, Integer> map) {
// 根据Context提供的map 根据key 获取对应的值 作为递归的出口 例如 map.get("a") = 1
return map.get(this.key);
}
}
-
// 符号表达式 为非终结表达式 此处仍然作为一个抽象类 因为符号有多种
public abstract class SymbolExpression extends AbstractExpression{
// 聚合AbstractExpression 作为左表达式和右表示式 均为AbstractExpression下的某一子类
// 左右表达式可能是终结表达式也可能是非终结表达式
// 如果是终结表达式 作为递归的出口 直接在map中获取值,如果是非终结表达式 将map向下传递进行递归 直至递归出口
protected AbstractExpression left;
protected AbstractExpression right;
public SymbolExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
}
// 子类一 减法表达式
class SubExpression extends SymbolExpression {
public SubExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
// 实现减法操作 左表达式的值 - 右表达式的值
@Override
public int interpreter(Map<String, Integer> map) {
return this.left.interpreter(map) - this.right.interpreter(map);
}
}
// 子类二 加法表达式
class AddExpression extends SymbolExpression {
public AddExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
// 实现加法操作 左表达式的值 + 右表达式的值
@Override
public int interpreter(Map<String, Integer> map) {
return this.left.interpreter(map) + this.right.interpreter(map);
}
}
// 如需*/等 直接加子类即可
-
// 计算器类 此处作为Context环境类 向解释器提供环境
public class Calculator {
private AbstractExpression abstractExpression;
public Calculator(String expression) {
// 使用栈 构建解释器 递归形式 始终保持栈中只会有一个元素(但嵌套了很多AbstractExpression)
Stack<AbstractExpression> abstractExpressionStack = new Stack<>();
char[] chars = expression.toCharArray();
AbstractExpression left = null;
AbstractExpression right = null;
for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case '+' :
// 是加法 在栈中取出左表达式 创建右表达式 构建AddExpression入栈
left = abstractExpressionStack.pop();
right = new VarExpression(String.valueOf(chars[++i]));
abstractExpressionStack.push(new AddExpression(left, right));
break;
case '-' :
// 是减法 在栈中取出左表达式 创建右表达式 构建SubExpression入栈
left = abstractExpressionStack.pop();
right = new VarExpression(String.valueOf(chars[++i]));
abstractExpressionStack.push(new SubExpression(left, right));
break;
default:
// 是字母 直接构建VarExpression 并入栈 作为递归的出口
abstractExpressionStack.push( new VarExpression(String.valueOf(chars[i])));
break;
}
}
// 在栈中取出构建好的表达式并赋值给成员变量
this.abstractExpression = abstractExpressionStack.pop();
}
// 使用Context中的环境进行解释(也就是计算表达式的值)
public int run(Map<String, Integer> map) {
return this.abstractExpression.interpreter(map);
}
}
-
public class Client {
public static void main(String[] args) {
System.out.println("请输入表达式:");
Scanner scanner = new Scanner(System.in);
String expr = scanner.nextLine();
// 表达式的数组
char[] chars = expr.toCharArray();
// 创建Map用于给解释器提供参数
Map<String, Integer> map = new HashMap<>();
for (char aChar : chars) {
String value;
if ('+' != aChar && '-' != aChar) {
if (!map.containsKey(String.valueOf(aChar))) {
System.out.println("请输入" + aChar + "的值:");
value = scanner.nextLine();
map.put(String.valueOf(aChar), Integer.valueOf(value));
}
}
}
// 使用计算器 进行计算 将构建好的map通过Context传递给解释器
Calculator calculator = new Calculator(expr);
System.out.println("表达式" + expr + "的计算结果是:" + calculator.run(map));
}
}
-
spring源码
-
在spring中的SpelExpressionParse中就使用到了解释器模式
-
测试代码
-
public class SpelExpressionParseTest {
public static void main(String[] args) {
SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
Expression expression = spelExpressionParser.parseExpression("(5 + 5) * 6");
Integer value = (Integer) expression.getValue();
System.out.println(value);
}
}
-
-
UML类图
-
-
说明
- TemplateAwareExpressionParser为ExpressionParser接口的实现类,实现了parseExpression方法,该方法中调用了parseTemplate方法和doParseExpression方法
- parseTemplate方法根据参数返回不同的解释器LiteralExpression和CompositeStringExpression
- 而doParseExpression方法为抽象方法,其实现在子类SpelParseExpression和InternalSpelExpressionParse中,其返回值都是SpelExpression
- Expression接口为解释器接口,其中有三个实现类,其中的getValue方法为解释方法
-
注意事项
- 当有一个语言需要解释执行,可将该语言中的句子表示为一个抽象语法树,就可以考虑使用解释器模式,让程序具有良好的扩展性
- 使用解释器可能会引起类膨胀、解释器模式采用递归调用方法,将会导致调试非常复杂、效率可能会降低