目录
一、基础概念
解释器模式是指给定一个语言(表达式),来表示它的文法,并定义一个解释器,使用该解释器来解释语言中的句子(表达式),并得到结果。简单来说,解释器模式把要处理的问题条条框框地定义出来,然后挨个细致解释,适合一些重复性的问题求解。
所谓终结符,是指表达式中的最小单位,它不再包含其他更小的表达式,可以理解为表达式的"叶子"节点,例如在一个简单的表达式计算器中:
1、数字值(如123)就是一个终结符,数字解释器实现了对数字的解释操作。
2、变量名(如a)也可以看作是一个终结符,变量解释器实现获取变量的值。
非终结符需要通过组合包含的子表达式的结果进行解释,例如:
1、加法表达式包含左边和右边两个子表达式,通过组合两个子表达式的计算结果实现加法。
2、函数调用表达式包含函数名和参数表达式,需要调用函数并传入参数计算结果。
举个例子:"5 + 6",然后解析这个表达式,计算出结果11。
在这个例子中:
1、数字5和6是终结符,它们是表达式的基本元素,不再包含其他表达式。
2、加号是非终结符,它表示一个表达式,需要通过计算5和6才能得出值。
所以简单来说:
1、终结符是表达式的基本元素,不再含其他表达式。
2、非终结符表示一个表达式,包含其他表达式。
二、UML类图
三、角色设计
角色 | 描述 |
---|---|
抽象表达式角色 | 定义解释器的接口,声明一个解释操作interpret() |
终结符表达式角色 | 实现抽象表达式接口,表示语法规则中的终结符 |
非终结符表达式角色 | 实现抽象表达式接口,表示语法规则中的非终结符,可以包含终结符和非终结符 |
客户端角色 | 建立解释器对象,并调用interpret()方法进行解释操作 |
四、案例分析
下面我们通过模拟一个算术表达式的运算过程来实现解释器模式。
大体的工作流程如下:
1、构建表达式语法树。
2、用递归解释器对语法树进行解释执行。
3、终结符表达式直接返回值,非终结符表达式通过组合运算达到计算功能。
4、客户端构建语法树并调用解释器执行,即完成了对表达式的解析和求值。
代码设计如下:
定义一个抽象表达式角色,并定义了解释操作接口interpret():
java
public interface Expression {
int interpret();
}
定义AddExpression,SubtractExpression等,非终结符表达式角色,它们实现了对复合表达式的解释,通过组合包含的子表达式的解释结果进行解释:
java
public class AddExpression implements Expression {
private Expression left;
private Expression right;
public AddExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() + right.interpret();
}
}
public class SubtractExpression implements Expression {
private Expression left;
private Expression right;
public SubtractExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() - right.interpret();
}
}
public class MultiplyExpression implements Expression {
private Expression left;
private Expression right;
public MultiplyExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
return left.interpret() * right.interpret();
}
}
public class DivideExpression implements Expression {
private Expression left;
private Expression right;
public DivideExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret() {
try {
return left.interpret() / right.interpret();
} catch (ArithmeticException e) {
System.out.println("除数不能为0");
return -1;
}
}
}
定义NumberExpression,终结符表达式角色,它实现了对终结符的解释,这里的终结符是数字,直接返回数字的值:
java
public class NumberExpression implements Expression {
private int number;
public NumberExpression(int number) {
this.number = number;
}
@Override
public int interpret() {
return number;
}
}
客户端角色,添加一个Interpreter类,来解析表达式字符串,构建抽象语法树,并调用interpret()方法:
java
public class Client {
public static void main(String[] args) {
Expression expression = new SubtractExpression(
new AddExpression(new NumberExpression(5),
new MultiplyExpression(new NumberExpression(10), new NumberExpression(15))),
new DivideExpression(new NumberExpression(5), new NumberExpression(5))
);
int result = expression.interpret();
System.out.println("计算结果为:" + result);
}
}
运行结果如下:
执行流程如下:
1、在客户端中,首先构建一个减法表达式SubtractExpression,它的左表达式是加法表达式AddExpression,右表达式是除法表达式DivideExpression。
2、加法表达式AddExpression的左表达式是数字表达式NumberExpression(5),右表达式是乘法表达式MultiplyExpression。
3、乘法表达式MultiplyExpression的左表达式是数字表达式NumberExpression(10),右表达式是数字表达式NumberExpression(15)。
4、除法表达式DivideExpression的左表达式是数字表达式NumberExpression(5),右表达式是数字表达式NumberExpression(5)。
5、这样就构建出了一个完整的表达式树。
6、然后调用这个减法表达式的interpret()方法进行解释执行。
7、interpret()方法会递归调用子表达式的interpret()方法,终结于数字表达式的interpret()只返回数字的值。
8、这样整个表达式树就得到了解释执行,得到最终结果。
9、这里的执行顺序是:数字表达式->乘法表达式->加法表达式->除法表达式->减法表达式,逐步对表达式树进行解释,得到最终的值。
所以这个案例利用了解释器模式,定义了表达式接口,并为不同的表达式实现了interpret()方法,通过组合模式构建表达式树,然后递归解释执行以求值。
五、总结
优点:
1、可扩展性好。新增解释表达式较为方便,符合开闭原则。
2、易于改变和扩展语法。比如增加新的运算符等。
3、将语法解释与执行分离,允许程序动态地定制,每个节点只需要处理自身逻辑。
4、各部分功能模块较为独立,有利于实现和维护。
5、使用递归解释方式简化了语法树的evaluate逻辑。
缺点:
1、解释过程效率较低,递归调用开销较大。
2、类数量多,之间依赖关系复杂,导致系统较难理解。
3、语法树构建较为麻烦,需要客户端理解语法结构。
4、语法树的解析需要定义大量的类,不易管理。
5、语言扩展较难,需要更改多个表达式类。
6、错误处理不容易,需要增加额外处理。
7、调试过程较复杂。
应用场景:
1、语言解释器:用于解析和解释语言中的表达式,如编译器、解释器等。这个代码案例就展示了如何通过解释器模式解析算术表达式。
2、表达式求值:用于进行表达式求值,如正则表达式、数学表达式、逻辑表达式等的求值运算。
3、符号处理引擎:可用来进行数学符号、表达式的解析。
4、语法分析:可构建语法树,用来分析句子或表达式所遵循的语法规则。
5、业务规则解析:可定义业务规则语法,并进行解释执行,用来实现规则引擎。
6、格式转换器:可将一种格式转换为另一种格式。
符合的设计原则:
1、开闭原则(Open Closed Principle)
解释器模式中的解释器语法可以通过继承扩展,而无需修改原有解释器代码,符合开闭原则。
2、单一职责原则(Single Responsibility Principle)
解释器模式把语法解析的任务分发给不同的解释器类,每一个解释器只负责一小部分语法规则,符合单一职责原则。
3、组合复用原则(Composite Reuse Principle)
解释器模式中的解释器可以通过组合方式重用,符合组合复用原则。
4、接口隔离原则(Interface Segregation Principle)
解释器模式通过定义解释器接口将解析语法和解释执行分离,符合接口隔离原则。
5、依赖倒转原则(Dependence Inversion Principle)
解释器类都依赖一个抽象语法树而不是具体语法类,符合依赖倒转原则。