引:无论是编译原理,还是解释型语言,都会涉及到解释,其思想也都和解释器模式类似。
定义
给定一门语言,定义它的文法的一组表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。——行为类
解释器模式的通用类图如下:
这里解释一下类图中的角色:
- AbstractExpression抽象解释器:具体的即时任务又各个实现类完成,具体的解释器分别由TerminalExpression(值)和NotermianlExpression(符号)完成。
- TerminalExpression终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但是有多个实例,对应不同的终结符。
- NotermianlExpression非终结符表达式:文法中的每条规则对应于一个非终结表达式。
- Context环境角色:存放数据
解释器是一个比较少用的模式,以下为其通用源码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41// 抽象表达式
public abstract class Expression {
// 每个表达式必须有一个解释任务
public abstract Object interpreter(Context ctx);
}
// 终结符表达式
public class TerminalExpression extends Expression {
// 通常终结符表达式只有一个,但是有多个对象
public Object interpreter(Context ctx) {
// 主要处理场景元素和数据的转换
return null;
}
}
// 非终结符表达式
public class NotermianlExpression extends Expression {
// 每个非终结符表达式都会对其他表达式产生依赖
public NotermianlExpression(Expression... expression) {}
public Object interpreter(Context ctx) {
// 进行文法处理
return null;
}
}
// 场景类
public class Client {
public static void main(String[] args) {
Context ctx = new Context();
// 通常定一个语法容器,容纳一个具体的表达式,通常为ListArray、LinkedList、Stack等容器
Stack<Expression> stack = null;
for(;;) {
// 进行语法判断,并产生递归调用
}
// 产生一个完整的语法树,由各个具体的语法分析进行解析
Expression exp = stack.pop();
// 具体元素进入场景
exp.interpreter(ctx);
}
}
通常Client是一个封装类,封装的结果就是传递进来的规范语法文件,解释器分析后产生结果并返回,避免了调用者与语法解析器的耦合关系。
应用
优点
解释器是一个简单语法分析工具,它最显著的有点就是扩展性,修改语法规则只要修改相应的非终结符表达式就可以了,若扩展语法,则只要增加非终结符类就可以了。
缺点
- 解释器模式会引起类膨胀(显而易见)
- 解释器采用递归调用方法,调试复杂,且影响效率。
使用场景
- 重复发生的问题可以使用解释器模式,如对不同的日志文件进行不同的分析。
- 一个简单语法需要解释的场景。
注意事项
尽量不要在重要的模块中使用解释器模式,否则维护回事一个很大的问题。
最佳实践
解释器模式在实际的系统开发中使用得非常少,因为它会引起效率问题、性能以及维护等问题,一般在大中型的框架型项目能够找到它的身影,如一些数据分析工具、报表设计工具、科学计算工具等,若你确实遇到“一种特定类型的问题发生的频率足够高”的情况下,准备使用解释器模式时,可以考虑一下Expression4J、MESP、Jep等开元的解析工具包。
参考
- 《设计模式之禅》