1. 定义
定义:给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。
解释器模式2. 使用场景
- 在项目中可以使用Shell、JRuby、Groovy等脚本语言代替解释器模式,弥补Java编译型语言的不足。一定要使用解释器的时候,可以使用Expression4J、MESP、Jep等开源分析工具包代替自己实现。
- 重复发生的问题可以使用解释器模式。例如对日志文件进行分析处理,由于各个服务器的日志格式不同,但是数据要素是相同的,按照解释器的说法就是终结符表达式都是相同的,但是非终结符表达式就需要制定。
- 一个简单语法需要解释的场景。解释器模式一般用来解析比较标准的字符集,例如SQL语法分析,但是该部分逐渐被专用工具所取代。
- 复杂语法的解释不推荐使用解释器模式,会带来类膨胀、调试困难、运行效率低等问题。
- 在某些特用的商业环境下也会采用解释器模式。例如金融模型、实验数据的计算等。
- Android中,对清单文件的解释就使用了解释器模式。
3. 实现
由于解释器模式在实际项目中使用使用得比较少,而且又不太好理解,所以通过具体的一个加减法计算器例子来实现一次。
抽象解释器(AbstractExpression):
/**
* 抽象表达式(解释器)(AbstractExpression)
* 具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成
*/
public abstract class Expression {
//每个表达式必须有一个解析任务
public abstract int interpreter(HashMap<String, Integer> var);
}
终结符表达式(TerminalExpression):
/**
* 终结符表达式(TerminalExpression)
* 终结符表达式又叫做运算元素、运算变量,也叫做终结符号,运算元素就是指a、b、c等符号,需要具体赋值的对象
* 终结符:这些元素除了需要赋值外,不需要做任何处理。所有运算元素都对应一个具体的业务参数,这是语法中最小的单元逻辑,不可再拆分
* 通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
*/
public class VarExpression extends Expression {
private String mKey;
public VarExpression(String key) {
this.mKey = key;
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.mKey);
}
}
非终结符表达式(NonterminalExpression):
/**
* 非终结符表达式(NonterminalExpression)
* 非终结符表达式又叫运算符号、做非终结符号
* 运算符号就是加减符号,需要我们编写算法进行处理,每个运算符号都要对应处理单元,否则公式无法运行
* 非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
*/
public abstract class SymbolExpression extends Expression {
//每个非终结符表达式都会对其他表达式产生依赖
protected Expression mLeft;
protected Expression mRight;
public SymbolExpression(Expression left, Expression right) {
this.mLeft = left;
this.mRight = right;
}
}
/**
* 加法
*/
public class AddExpression extends SymbolExpression{
public AddExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return this.mLeft.interpreter(var) + this.mRight.interpreter(var);
}
}
/**
* 减法
*/
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return this.mLeft.interpreter(var) - this.mRight.interpreter(var);
}
}
封装类:
/**
* 封装类
* 将需要的操作封装到一个类中,符合单一职责原则
*/
public class Calculator {
private Expression mExpression;
//通过递归的方式来解释表达式
public Calculator(String expStr) {
Stack<Expression> stack = new Stack<>();
char[] charArray = expStr.toCharArray();
Expression left;
Expression right;
for (int i = 0; i < charArray.length; i++) {
switch (charArray[i]) {
case '+':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new AddExpression(left, right));
break;
case '-':
left = stack.pop();
right = new VarExpression(String.valueOf(charArray[++i]));
stack.push(new SubExpression(left, right));
break;
default:
stack.push(new VarExpression(String.valueOf(charArray[i])));
break;
}
}
//解析结果赋值给成员变量,通过calculate方法进行运算、得到计算结果
this.mExpression = stack.pop();
}
//计算结果
public int calculate(HashMap<String, Integer> var) {
return this.mExpression.interpreter(var);
}
//获取表达式
public static String getExpStr() throws Exception {
System.out.println("请输入表达式:");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
return reader.readLine();
}
//为表达式中的运算符号赋值
public static HashMap<String, Integer> getValues(String expStr) throws Exception {
HashMap<String, Integer> values = new HashMap<>();
for (char ch : expStr.toCharArray()) {
if (ch != '+' && ch != '-') {
String key = String.valueOf(ch);
//这里需要防止同样的元素被重复赋值导致值被覆盖
if (!values.containsKey(key)) {
System.out.println("请输入" + key + "的值:");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
Integer value = Integer.valueOf(reader.readLine());
values.put(key, value);
}
}
}
return values;
}
}
场景类:
public class Client {
public static void main(String[] args) throws Exception {
//获取表达式
String expStr = Calculator.getExpStr();
//为表达式中的元素赋值,这里的HashMap充当了Context上下文角色
HashMap<String, Integer> values = Calculator.getValues(expStr);
//构造Calculator,解释表达式
Calculator calculator = new Calculator(expStr);
//运算
int result = calculator.calculate(values);
System.out.println("计算结果为:" + result);
}
}
运行结果:
请输入表达式:
a+b-c
请输入a的值:
1
请输入b的值:
2
请输入c的值:
3
计算结果为:0
4. 优点
- 公式可以运行时编辑。
- 高扩展性。修改语法规则只要修改相应的非终结符表达式就可以了;若扩展语法,则只要增加非终结符类就可以了。
5. 缺点
- 语法规则比较复杂时,解释器模式会引起类膨胀,维护困难。
- 解释器模式采用了循环、递归调用等方法,会带来效率、调试困难等问题。
网友评论