给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释文法中的句子。
一、什么是解释器模式?
解释器这个名词想必大家都不会陌生,比如编译原理中,一个算术表达式通过词法分析器形成词法单元,而后这些词法单元再通过语法分析器构建语法分析树,最终形成一颗抽象的语法分析树。诸如此类的例子也有很多,比如编译器、正则表达式等等。
如正则表达式,它就是解释器模型的一种应用,解释器为正则表达式定义了一个文法,如何表示一个特定的正则表达式,以及如何解释这个正则表达式。
如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子,这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
UML结构图如下:
重点就是给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
-
AbstractExpression:抽象解释器
声明一个抽象的解释操作,这个接口为抽象语法树中所有的节点所共享。 -
TerminalExpression:终结解释器
实现与文法中的终结符相关联的解释操作。一个句子中的每一个终结符需要该类的一个实例。 -
NonterminalExpression:非终结解释器
对文法中的规则的解释操作。 -
Context:环境角色
包含解释器之外的一些全局信息。 -
Client:客户端
构建表示该语法定义的语言中一个特定的句子的抽象语法树,并调用解释操作。
代码框架如下:
public abstract class Expression {
public abstract String interpret(Context context);
}
public class TerminalExpression extends Expression {
@Override
public String interpret(Context context) {
return "终端解释器";
}
}
public class NonTerminalExpression extends Expression {
@Override
public String interpret(Context context) {
return "非终端解释器";
}
}
public class Context {
private Map<String, Expression> map;
public String getValue(String key){
return map.get(key).interpret(this);
}
}
public class Client {
public static void main(String[] args) {
Context context = new Context();
List<Expression> list = new ArrayList<>();
list.add(new TerminalExpression());
list.add(new NonTerminalExpression());
list.add(new TerminalExpression());
list.add(new TerminalExpression());
for (Expression abstractExpression : list) {
Console.log(abstractExpression.interpret(context));
}
}
}
终端解释器
非终端解释器
终端解释器
终端解释器
其中list为一个语法容器,容纳一个具体的表达式。通常Client是一个封装类,封装的结果就是传递进来一个规范语法文件,解析器分析后产生结果并返回,避免了调用者与语法分析器的耦合关系。
二、代码实例
通过解释器模式来实现四则运算,如计算a+b的值。UML图如下:
public abstract class Expression {
// 解析公式和数值,key是公式中的参数,value是具体的数值
public abstract int interpreter(HashMap<String, Integer> var);
}
变量解析器:
@AllArgsConstructor
public class VarAnalysis extends Expression {
private String key;
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
抽象运算符号解析器:
@AllArgsConstructor
public class SymbolAnalysis extends Expression {
protected Expression left;
protected Expression right;
@Override
public int interpreter(HashMap<String, Integer> var) {
return 0;
}
}
加法解析器:
public class AddAnalysis extends SymbolAnalysis {
public AddAnalysis(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
减法解析器:
public class SubAnalysis extends SymbolAnalysis {
public SubAnalysis(Expression left, Expression right) {
super(left, right);
}
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
解析器封装类:
public class Calculator {
//定义表达式
private Expression expression;
//构造函数传参,并解析
public Calculator(String expStr) {
//安排运算先后顺序
Stack<Expression> stack = new Stack<>();
//表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
Expression left = null;
Expression right = null;
for(int i=0; i<charArray.length; i++) {
switch (charArray[i]) {
case '+': //加法
left = stack.pop();
right = new VarAnalysis(String.valueOf(charArray[++i]));
stack.push(new AddAnalysis(left, right));
break;
case '-': //减法
left = stack.pop();
right = new VarAnalysis(String.valueOf(charArray[++i]));
stack.push(new SubAnalysis(left, right));
break;
default: //公式中的变量
stack.push(new VarAnalysis(String.valueOf(charArray[i])));
break;
}
}
this.expression = stack.pop();
}
//计算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
public class Client {
public static void main(String[] args) throws IOException {
String expStr = getExpStr();
HashMap<String, Integer> var = getValue(expStr);
Calculator calculator = new Calculator(expStr);
System.out.println("运算结果:" + expStr + "=" + calculator.run(var));
}
//获得表达式
public static String getExpStr() throws IOException {
System.out.print("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
public static HashMap<String, Integer> getValue(String expStr) throws IOException {
HashMap<String, Integer> map = new HashMap<>();
for(char ch : expStr.toCharArray()) {
if(ch != '+' && ch != '-' ) {
if(! map.containsKey(String.valueOf(ch))) {
System.out.print("请输入" + String.valueOf(ch) + "的值:");
String in = (new BufferedReader(new InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
请输入表达式:a-b+c
请输入a的值:3
请输入b的值:2
请输入c的值:1
运算结果:a-b+c=2
Calculator构造函数传参,并解析封装。这里根据栈的“先进后出”来安排运算的先后顺序(主要用在乘除法,这里只写了加减法比较简单)。以加法为例,Calculator构造函数接收一个表达式,然后把表达式转换为char数组,并判断运算符号,如果是‘+’则进行加法运算,把左边的数(left变量)和右边的数(right变量)加起来即可。
例如a+b-c这个表达式,根据for循环,首先被压入栈中的是a元素生成的VarExpression对象,然后判断到加号时,把a元素的对象从栈中pop出来,与右边的数组b进行相加,而b是通过当前的数组游标下移一个单元格得来的(为了防止该元素被再次遍历,通过++i的方式跳过下一遍历)。减法运算同理。
三、适用场景
优点:
- 可扩展性比较好,灵活。
- 增加了新的解释表达式的方式。
- 易于实现简单文法。
缺点:
- 可利用场景比较少。
- 对于复杂的文法比较难维护。
- 解释器模式会引起类膨胀。
- 解释器模式采用递归调用方法。
使用场景:
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 一些重复出现的问题可以用一种简单的语言来进行表达。
- 一个简单语法需要解释的场景。
总的来说,如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。