定义
给定一门语言,定义它文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子
解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。诸如此类的例子也有很多,比如编译器、正则表达式等等。
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
就比如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
结构和实现
结构
- Context是环境角色,包含解释器之外的一些全局信息;
- AbstractExpression为抽象表达式,声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享;
- TerminalExression为终结符表达式,实现与文法中的终结符相关联的解释操作;
- NonterminalExpression为非终结符表达式,为文法中的非终结符实现解释操作,对文法中每一条规则R1、R2……Rn都需要一个具体的非终结符表达式类。
实现
Context环境角色
public class Context {
private String input;
private String output;
public String getInput() {
return input;
}
public void setInput(String input) {
this.input = input;
}
public String getOutput() {
return output;
}
public void setOutput(String output) {
this.output = output;
}
}
抽象表达式
抽象表达式是生成语法集合(语法树)的关键,每个语法集合完成指定语法解析任务,它是通过递归调用的方式,最终由最小的语法单元进行解析完成。
public abstract class AbstractExpression {
public abstract void Interpret(Context context);
}
终结符表达式
通常,终结符表达式比较简单,主要处理场景元素和数据的转换。
public class TerminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
System.out.println("终端解释器");
}
}
非终结符表达式
每个非终结符表达式都代表了一个文法规则,并且每个文法规则都只关心自己周边的文法规则的结果,因此这就产生了每个非终结符表达式调用自己周边的非终结符表达式,然后最终、最小的文法规则就是终结符表达式。
public class NonterminalExpression extends AbstractExpression {
@Override
public void Interpret(Context context) {
System.out.println("非终端解释器");
}
}
Client客户端
public class Client {
public static void main(String[] args) {
Context context = new Context();
List<AbstractExpression> list = new ArrayList<>();
list.add(new TerminalExpression());
list.add(new NonterminalExpression());
list.add(new TerminalExpression());
list.add(new TerminalExpression());
for (AbstractExpression abstractExpression : list) {
abstractExpression.Interpret(context);
}
}
}
总结
优点
- 可扩展性好:修改语法规则只需要修改相应的非中介符表达式即可,若拓展语法,则只要增加非终结符就可以了
缺点
- 解释器模式会引起类膨胀:每个语法都要产生一个非终结符表达式,规则复杂时,就可能产生大量类文件
- 采用了递归
- 效率问题:采用了大量的循环和递归,效率不高
使用场景
- 重复发生的问题可以使用解释器模式:比如不同的服务器都会产生不同格式的日志文件,但其中的数据要素是相同的。用解释器模式的意思来说终结符表达式都是相同的,只需要根据服务器指定对应的非终结符表达式即可完成解析
注意事项
因为效率以及维护问题。重要的场景不要使用解释器模式,可以使用脚本语言来完成解析。使用脚本语言来代替,效率和性能各方面表现良好。
准备使用解释器模式时,可以考虑一下Expression4J、MESP、Jep等开源解析工具包,功能强大且易于使用,没必要自己从头开始写解释器