java 设计模式之解释器模式①⑨
躁动的心灵,不安的灵魂。在彷徨中挣扎,在迷失中探索。时光飞逝,努力不变。
设计模式学习,近期我会把23种设计模式都写成博客,敬请期待~
—2021/1/26
定义
定义了一个解释器,来解释给定语言和文法的句子。其实质是把语言中的每个符号定义成一个(对象)类,从而把每个程序转换成一个具体的对象树.(编译原理上的编译器)
举例理解:
要想和外国人交流,就得通过他们的语言,比如’你好’,把你好传递给翻译官,翻译官翻译出来之后告诉外国人,这里的翻译官就扮演了解释器的责任.
角色分析
UML类图(1.1):
- 抽象解释器(Expression) :具体的解释任务由各个实现类完成。
- 终结符表达式(VarExpression) :实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结表达式,但有多个实例,对应不同的终结符。
- 非终结符表达式(SymbolExpression) :文法中的每条规则对应于一个非终结表达式,非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式
- 上下文(Calculator) : 上下文环境类,包含解释器之外的全局信息
- 客户类: 客户端,解析表达式,构建抽象语法树,执行具体的解释操作等.
注:
这里的角色不是固定的,以实际场景来确定
非终结符表达式和终结符表达式区别:
- 非终结符表达式(相当于树的树杈),还会向下执行
- 终结符表达式(相当于树叶子),不会向下执行
使用场景
- 一些重复出现的问题可以用一种简单的语言来表达,
- 一个简单语法需要解释的场景
正则表达式就是采用的解释器模式,但是解释器模式相对于其他的设计模式来说,使用还是太少了!,了解即可.
代码实现
Expression(抽象解释器):
public abstract class Expression {
/**
*
* @param var key: a,b,c | value: 1,2,3...
*/
public abstract int interpreter(HashMap<String,Integer> var);
}
SymbolExpression非终结符表达式:
public abstract class SymbolExpression extends Expression {
protected Expression left;
protected Expression right;
//所有的解析公式都应只关心自己左右两个表达式的结果
public SymbolExpression(Expression _left,Expression _right){
this.left = _left;
this.right = _right;
}
}
这个类只关心运算符左右的值,比如a + b,他只关心 + 号左右的 a 和 b ,具体运算交给其子类
AddExpression(加法运算符):
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);
}
}
通过父类(SymbolExpression)中的left和right相加即可
SubExpression(减法运算符):
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);
}
}
通过父类(SymbolExpression)中的left和right相加即可
VarExpression(终结表达式,没有子类!)
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);
}
}
这里传递的_key就是对应的字母,假设现在求a+b,这里的_key就是a,b
Calculator获取表达式,计算结果(这个类是关键!)
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])));
}
}
//把运算结果抛出来
this.expression = stack.pop();
}
//开始运算
public int run(HashMap<String, Integer> var) {
return this.expression.interpreter(var);
}
}
分析:
假设现在表达式还是:a+b
当创建Calculator(String expStr)时,传入的就是"a+b"
然后把"a+b"拆分成[a , + , b] 数组,让其来循环
第一次循环 a ,使用栈来压入栈底
第二次循环 + 号,首先执行left = stack.pop();方法,因为当前栈内只有一个,所以此时left等于a,
然后把它赋值给终结者表达式VarExpression(String.valueOf(charArray[++i])) 此时 i == 3,然后把a和b赋值给加法表达式(AddExpression())让他相加即可
所以这里a+b,只循环2次!
测试代码(客户端):
解释器模式 经典案例 \\\\\\\\\\\\\\\\\\\\\\\
HashMap<String, Integer> map = new HashMap<>();
map.put("a",23);
map.put("b",12);
Calculator cal = new Calculator("a+b");
System.out.println( "运算结果为:" + cal.run(map));
Log图(2.1):
总结:
-
优点:
- 扩展性强,若要新增乘,除,添加相应的非终结表达式,修改计算逻辑即可。
-
缺点
- 需要建大量的类,因为每一种语法都要建一个非终结符的类。
- 解释的时候采用递归调用方法,导致有时候函数的深度会很深,影响效率。
总得来说,解释器模式用的比较少,理解即可.
原创不易,您的点赞就是对我最大的支持~