1.定义
解释器模式是一种行为型模式,工作中基本上是用不到的,他的作用就是给定一个语言,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
现在有一个需求,大概就是计算器,首先给定一个公式,然后输入对应的值计算出最终的结果,公式不复杂,只能支持 和-操作。例如:输入a b-c,100、200、100,然后计算结果为200。其中我们输入的a b-c实际上可以理解为是另一种语言,那么此时就需要一个解释器进行语法解释,并计算结果。
现在需求有了我们分析一下公式中有哪些元素,其中有运算符以及参与运算的值,而对于解释器来说实际上只需要解释运算符以及参与运算的值即可,当然运算符可能又存在各种不同的运算符。例如加和减实际上对应的计算规则是不同的。
2.解释器模式结构图
VarExpression 用来解析运算的值,各个公式的运算元素的数量是不同的,每个运 算元素对应了一个 VarExpression 对象,SybmolExpression 是负责运算符号解析的,分别有两个子类 AddExpression(负责加法运算)和 SubExpression(负责减法运算)来实现。
Calculator类用于安排运算的先后顺序(加减法是不用考虑,但是乘除法呢?注意扩展性),并且还要返回结果,因此 我们需要增加一个封装类来处理进行封装,由于我们只作运算,暂时还不与业务有挂钩。
3.解释器模式实现
Expression抽象类,只是定义了一个方法interpreter方法用于进行计算,当然计算的逻辑由子类去实现,而参数HashMap中存放的key就是公式中的参数,例如a、b、c而value则代表的是对用的参数具体的值。
代码语言:javascript复制public abstract class Expression {
//解析公式和数值,其中var中的key值是是公式中的参数,如a、b、c,value值是具体的数字
public abstract int interpreter(HashMap<String,Integer> var);
}
VarExpression是Expression的具体实现,用于解析公式中的具体运算值,如a、b、c具体对应的值是多少,当然具体的值就是通过key从HashMap中进行获取的。
代码语言:javascript复制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);
}
}
SymbolExpression也是Expression的具体实现,用于解析公式运算符,当然对于运算符来说肯定需要知道左边是谁和右边是谁,不然怎么进行计算呢。
代码语言:javascript复制public abstract class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
//所有的解析公式都应只关心自己左右两个表达式的结果
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
}
AddExpression是具体的运算符解析器,是对SymbolExpression的具体实现,对于SymbolExpression来说只需要定义运算的左右两部分,而实际要加还是要减是子类来做的,所以AddExpression就是将左右两部分进行相加。
代码语言:javascript复制public class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
//把左右两个表达式运算的结果加起来
@Override
public int interpreter(HashMap<String, Integer> var) {
return left.interpreter(var) right.interpreter(var);
}
}
而是说SubExpression则是将运算符的左右部分做减法。
代码语言:javascript复制public class SubExpression extends SymbolExpression {
public SubExpression(Expression left, Expression right) {
super(left, right);
}
//左右两个表达式相减
@Override
public int interpreter(HashMap<String, Integer> var) {
return left.interpreter(var) - right.interpreter(var);
}
}
Calculator构造函数接受一个表达式,然后把表达式转化为 char 数组, 并判断运算符号,如果是“ ”则调用AddExpression,把左边的数(left 变量)和右边的数(right 变量)放入AddExpression。并且存放到Stack(栈)中。
那左边的数为什么是在堆栈中呢?原因是因为放入栈中是为了让计算有先后顺序,例如a b-c,如果栈中已经有了a b的AddExpression,那么如果再减法的时候应该是(a b)作为左边,而c作为右边放入SubExpression。
代码语言:javascript复制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 ' ': //加法
//拿到加法的左边值,如a b-c,拿到a
left = stack.pop();
//拿到加法的右边值,如a b-c,拿到b
right = new VarExpression(String.valueOf(charArray[ i]));
//将做加法的实体存入栈中
stack.push(new AddExpression(left, right));
break;
case '-':
//拿到减法的左边值,如a b-c,拿到(a b)
left = stack.pop();
//拿到减法的右边值,如a b-c,拿到c
right = new VarExpression(String.valueOf(charArray[ i]));
//将做减法的实体存入栈中
stack.push(new SubExpression(left, right));
break;
default: //公式中的变量,即a或b或c
stack.push(new VarExpression(String.valueOf(charArray[i])));
}
}
//将需要计算的实体取出来,如a b-c,则拿到(a b)的实体和(a b)-c的实体
this.expression = stack.pop();
}
//开始运算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
Test类来模拟用户情况,用户要求可以扩展,可以修改公式,那就通过接受键盘事件来处理,Client 类的源代码如下。
代码语言:javascript复制public class Test {
//运行四则运算
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.print("请输入表达式:");
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))) { //解决重复参数的问题
System.out.print("请输入" ch "的值:");
String in = (new BufferedReader(new
InputStreamReader(System.in))).readLine();
map.put(String.valueOf(ch), Integer.valueOf(in));
}
}
}
return map;
}
}
代码语言:javascript复制请输入表达式:a b-c
请输入a的值:100
请输入b的值:200
请输入c的值:20
运算结果为:a b-c=280
解释器模式中的角色
AbstractExpression(抽象解释器),具体的解释任务由各个实现类完成。
concreteExpression(具体的解释器)具体的解释器分为两大类:TerminalExpression:终结符表达式,实现与文法中的元素相关联的解释操作,通常一个解释器模式 中只有一个终结符表达式,但有多个实例,对应不同的终结符。具体到我们例子就是 VarExpression 类, 表达式中的每个终结符都在堆栈中产生了一个 VarExpression 对象。 NonterminalExpression(非终结符表达式),文法中的每条规则对应一个非终结表达式,具体到我们的 例子就是加减法规则分别对应到 AddExpression 和 SubExpression 两个类。非终结符表达式根据逻辑的复 杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。 Context(环境角色),具体到我们的例子中是采用 HashMap 代替。
参考文献《设计模式之禅》
代码获取地址:https://gitee.com/bughong/design-pattern