解释器模式
解释器模式(Interpreter Pattern):给定一门语言,定义它的文法的一种表示,并定义一个解释器,该解释器使用该表示来解释语言中的句子。
解释器模式是一种按照规定语法进行解析的方案,在现在项目中使用较少。
解释器模式中的角色:
- AbstractExpression抽象解释器:具体的解释任务由各个实现类完成,具体的解释器分别由TerminalExpression和NonterminalExpression完成。
- TerminalExpression终结符表达式:实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符。
- NonterminalExpression非终结符表达式:文法中的每条规则对应于一个非终结表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
- Context环境角色。
抽象表达式Expression:
public abstract class Expression {
//每个表达式必须有一个解析任务
public abstract Object interpreter(Context ctx);
}
终结符表达式TerminalExpression:
public class TerminalExpression extends Expression {
//通常终结符表达式只有一个,但是有多个对象
public Object interpreter(Context ctx) {
return null;
}
}
非终结符表达式NonterminalExpression:
public class NonterminalExpression extends Expression {
//每个非终结符表达式都会对其他表达式产生依赖
public NonterminalExpression(Expression... expression){
}
public Object interpreter(Context ctx) {
//进行文法处理
return null;
}
}
客户类Client:
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是一个封装类,封装的结果就是传递进来一个规范语法文件,解析器分析后产生结果并返回,避免了调用者与语法解析器的耦合关系。
解释器模式的优点
扩展性:修改语法规则,只要修改相应的非终结符表达式;扩展语法,则只要增加非终结符类。
解释器模式的缺点
- 解释器模式会引起类膨胀:每个语法都要产生一个非终结符表达式,语法规则比较复杂时,就可能产生大量的类文件,为维护带来了非常多的麻烦。
- 解释器模式采用递归调用方法:每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,必须一层一层地剥茧,无论是面向过程的语言还是面向对象的语言,递归都是在必要条件下使用的,它导致调试非常复杂。
- 效率问题:解释器模式使用了大量的循环和递归,当解析复杂、冗长的语法时,效率是难以忍受的。
解释器模式使用的场景
- 重复发生的问题可以使用解释器模式
- 一个简单语法需要解释的场景
解释器模式的注意事项
尽量不要在重要的模块中使用解释器模式,否则维护会是一个很大的问题。在项目中可以使用shell、JRuby、Groovy等脚本语言来代替解释器模式,弥补Java编译型语言的不足。
解释器模式的实例
业务需求:输入一个模型公式(加、减运算),然后输入模型中的参数,运算出结果。
设计要求:
- 公式可以运行时编辑,并且符合正常算术书写方式,例如a+b-c。
- 高扩展性,未来增加指数、开方、极限、求导等运算符号时较少改动。
- 效率可以不用考虑,晚间批量运算。
(1)抽象表达式类
import java.util.HashMap;
public abstract class Expression {
//解析公式和数值,其中var中的key值是公式中的参数,value值是具体的数字
public abstract int interpreter(HashMap<String, Integer> var);
}
(2)变量解析器
import java.util.HashMap;
import com.sfq.impl.Expression;
public class VarExpression extends Expression {
private String key;
public VarExpression(String key) {
this.key = key;
}
//从map中取之
@Override
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
(3)抽象运算符号解析器
public abstract class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
(4)加法解析器
import java.util.HashMap;
import com.sfq.impl.Expression;
import com.sfq.impl.SymbolExpression;
public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//把左右两个表达式运算的结果加起来
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
(5)减法解析器
import java.util.HashMap;
import com.sfq.impl.Expression;
import com.sfq.impl.SymbolExpression;
public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap<String, Integer> var) {
return super.left.interpreter(var) - super.right.interpreter(var);
}
}
(6)解析器封装类
import java.util.HashMap;
import java.util.Stack;
import com.sfq.impl.Expression;
public class Calculator {
private Expression expression;
public Calculator(String expStr) {
//定义一个栈,安排运算的前后顺序
Stack<Expression> stack = new Stack<Expression>();
//表达式拆分成字符串
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 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;
}
}
//抛出运算结果
this.expression = stack.pop();
}
//开始运算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
(7)客户模拟类
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;
import com.sfq.action.Calculator;
public class Client {
//运行四则运算
public static void main(String[] args) throws IOException{
String expStr = getExpStr();
//赋值
HashMap<String, Integer> var = getValue(expStr);
Calculator cal = new Calculator(expStr);
System.out.println("运算结果为:" + expStr + "=" + cal.run(var));
}
//获得表达式
public static String getExpStr() throws IOException{
System.out.println("请输入表达式:");
return (new BufferedReader(new InputStreamReader(System.in))).readLine();
}
//获得值映射
public static HashMap<String, Integer> getValue(String exprStr) throws IOException{
HashMap<String, Integer> map = new HashMap<String,Integer>();
//解析有几个参数要传递
for(char ch:exprStr.toCharArray()) {
if (ch != '+' && ch != '-') {
//解决重复参数的问题
if (!map.containsKey(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
100
20
40
运算结果为:a+b-c=80
公式是在运行时定义的,而不是在运行前就制定好的,是不是类似于初中学过的“代数”这门课?先公式,然后赋值,运算出结果。
若需要扩展也非常容易,只要增加SymbolExpression的子类就可以了。