目录
1 解释器模式介绍
解释器模式使用频率不算高,通常用来描述如何构建一个简单“语言”的语法解释器。它只在一些非常特定的领域被用到,比如编译器、规则引擎、正则表达式、SQL 解析等。不过,了解它的实现原理同样很重要,能帮助你思考如何通过更简洁的规则来表示复杂的逻辑。
解释器模式(Interpreter pattern)的原始定义是:用于定义语言的语法规则表示,并提供解释器来处理句子中的语法。
- 假设我们设计一个软件用来进行加减计算。我们第一想法就是使用工具类,提供对应的加法和减法的工具方法。
//用于两个整数相加的方法
public static int add(int a , int b){
return a + b;
}
//用于三个整数相加的方法
public static int add(int a , int b,int c){
return a + b + c;
}
public static int add(Integer ... arr){
int sum = 0;
for(Integer num : arr){
sum += num;
}
return sum;
}
+ -
2 解释器模式原理
- 抽象表达式(Abstract Expression)角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式(Terminal Expression)角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每一个终结符都有一个具体终结表达式与之相对应。上例中的value 是终结符表达式.
- 非终结符表达式(Nonterminal Expression)角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。上例中的 plus , minus 都是非终结符表达式
- 环境(Context)角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端(Client):主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
3 解释器模式实现
代码示例:
- 用户按照上 面的规则书写表达式,传递给 interpret() 函数,就可以得到最终的计算结果。
/**
* 表达式解释器类
* @author spikeCong
* @date 2022/10/20
**/
public class ExpressionInterpreter {
//Deque双向队列,可以从队列的两端增加或者删除元素
private Deque<Long> numbers = new LinkedList<>();
//接收表达式进行解析
public long interpret(String expression){
String[] elements = expression.split(" ");
int length = elements.length;
//获取表达式中的数字
for (int i = 0; i < (length+1)/2; ++i) {
//在 Deque的尾部添加元素
numbers.addLast(Long.parseLong(elements[i]));
}
//获取表达式中的符号
for (int i = (length+1)/2; i < length; ++i) {
String operator = elements[i];
//符号必须是 + - * / 否则抛出异常
boolean isValid = "+".equals(operator) || "-".equals(operator)
|| "*".equals(operator) || "/".equals(operator);
if (!isValid) {
throw new RuntimeException("Expression is invalid: " + expression);
}
//pollFirst()方法, 移除Deque中的第一个元素,并返回被移除的值
long number1 = numbers.pollFirst(); //数字
long number2 = numbers.pollFirst();
long result = 0; //运算结果
//对number1和number2进行运算
if (operator.equals("+")) {
result = number1 + number2;
} else if (operator.equals("-")) {
result = number1 - number2;
} else if (operator.equals("*")) {
result = number1 * number2;
} else if (operator.equals("/")) {
result = number1 / number2;
}
//将运算结果添加到集合头部
numbers.addFirst(result);
}
//运算完成numbers中应该保存着运算结果,否则是无效表达式
if (numbers.size() != 1) {
throw new RuntimeException("Expression is invalid: " + expression);
}
//移除Deque的第一个元素,并返回
return numbers.pop();
}
}
代码重构
- NumExpression
- PluExpression
- SubExpression
- MulExpression
- DivExpression
/**
* 表达式接口
* @author spikeCong
* @date 2022/10/20
**/
public interface Expression {
long interpret();
}
/**
* 数字表达式
* @author spikeCong
* @date 2022/10/20
**/
public class NumExpression implements Expression {
private long number;
public NumExpression(long number) {
this.number = number;
}
public NumExpression(String number) {
this.number = Long.parseLong(number);
}
@Override
public long interpret() {
return this.number;
}
}
/**
* 加法运算
* @author spikeCong
* @date 2022/10/20
**/
public class PluExpression implements Expression{
private Expression exp1;
private Expression exp2;
public PluExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() + exp2.interpret();
}
}
/**
* 减法运算
* @author spikeCong
* @date 2022/10/20
**/
public class SubExpression implements Expression {
private Expression exp1;
private Expression exp2;
public SubExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() - exp2.interpret();
}
}
/**
* 乘法运算
* @author spikeCong
* @date 2022/10/20
**/
public class MulExpression implements Expression {
private Expression exp1;
private Expression exp2;
public MulExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() * exp2.interpret();
}
}
/**
* 除法
* @author spikeCong
* @date 2022/10/20
**/
public class DivExpression implements Expression {
private Expression exp1;
private Expression exp2;
public DivExpression(Expression exp1, Expression exp2) {
this.exp1 = exp1;
this.exp2 = exp2;
}
@Override
public long interpret() {
return exp1.interpret() / exp2.interpret();
}
}
//测试
public class Test01 {
public static void main(String[] args) {
ExpressionInterpreter e = new ExpressionInterpreter();
long result = e.interpret("6 2 3 2 4 / - + *");
System.out.println(result);
}
}
4 解释器模式总结
1) 解释器优点
-
易于改变和扩展文法
因为在解释器模式中使用类来表示语言的文法规则的,因此就可以通过继承等机制改变或者扩展文法.每一个文法规则都可以表示为一个类,因此我们可以快速的实现一个迷你的语言
-
实现文法比较容易
在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂
-
增加新的解释表达式比较方便
如果用户需要增加新的解释表达式,只需要对应增加一个新的表达式类就可以了.原有的表达式类不需要修改,符合开闭原则
2) 解释器缺点
-
对于复杂文法难以维护
在解释器中一条规则至少要定义一个类,因此一个语言中如果有太多的文法规则,就会使类的个数急剧增加,当值系统的维护难以管理.
-
执行效率低
在解释器模式中大量的使用了循环和递归调用,所有复杂的句子执行起来,整个过程也是非常的繁琐
3) 使用场景
- 当语言的文法比较简单,并且执行效率不是关键问题.
- 当问题重复出现,且可以用一种简单的语言来进行表达
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象的语法树的时候