(十三),解释器模式

最后更新于:2022-04-01 09:57:24

解释器模式,从字面上解释来说就是为一个文法(具有特定语法的形式的语句或表达式)构造解释器,这个解释器用来解释这个文法,使得这种具有某种书写规则的文法能够表示特定的功能,这种特定书写规则也就是通常所说的语法,如C/C++,Java,Python等计算机语言有自己的语法。还有,一些解释型语言如Python,它在运行的时候需要Python解释器,这也就是一种解释器。 定义:解释器模式为一些具有特定书写规则的文法编写解释器,从而使得它具有某种意义。 使用场景: 1. 作为解释器。解释具有特定语法的语句的功能或者意义。如解释具有特定书写规则的语句,在下面的这个例子中就是作为解释器来解释运算表达式; 1. 作为翻译机或者译码器。如高级语言中的常量,编译的时候编译器系统会将常量替换成它代表的数一样,类似的我们也可以定义一些特定的具有某种意义的语句,然后用相应的解释器来解释器它。 假设又这么个表达式或语句a_+_b_-_c,我们想要知道它的意思,假如我们规定_符号用来分隔数字和运算符,那么上面的这个语句表达的意思就是计算a+b-c,这和学生考试的时候,它们规定1表示A,2表示B,3表示C,4表示D来传选择题答案一样的,类似于编码译码过程,其实译码器也就是解释器,那么我们通过代码来解释上面表达式的意思。 代码实现: 抽象解释器(基类) ~~~ /** * 基本的解释器,是所有解释器的基类 * @author lt * */ public abstract class BaseInterpreter<T> { /** * 抽象解释方法 * @return */ public abstract T interpret(); } ~~~ 抽取了解释器的共性 整数解释器(解释整数) ~~~ /** * 整数解释器 * @author lt * */ public class NumberInterpreter extends BaseInterpreter<Integer>{ private int num; public NumberInterpreter(int num){ this.num = num; } @Override public Integer interpret() { return this.num; // 自动装箱拆箱 } } ~~~ 解释整数,直接返回这个整数,这里的整数也就是终结符 运算符解释器(基类) ~~~ /** * 二目运算符操作解释器,也是一个基类,因为有好多的二目运算符 * @author lt * */ public abstract class OperatorInterpreter extends BaseInterpreter<Integer>{ protected BaseInterpreter<Integer> exp1; protected BaseInterpreter<Integer> exp2; public OperatorInterpreter(BaseInterpreter<Integer> exp1,BaseInterpreter<Integer> exp2){ this.exp1 = exp1; this.exp2 = exp2; } } ~~~ 解释二目运算符,需要两个整数,为非终结符解释器。 加法解释器(计算加法) ~~~ /** * 加法解释器,计算加法 * @author lt * */ public class AdditionInterpreter extends OperatorInterpreter{ public AdditionInterpreter(BaseInterpreter<Integer> exp1, BaseInterpreter<Integer> exp2) { super(exp1, exp2); } /** * 用来计算加法 */ @Override public Integer interpret() { return exp1.interpret() + exp2.interpret(); } } ~~~ 减法解释器(计算减法) ~~~ /** * 减法计算器 * @author lt * */ public class SubtractionInterpreter extends OperatorInterpreter{ public SubtractionInterpreter(BaseInterpreter<Integer> exp1, BaseInterpreter<Integer> exp2) { super(exp1, exp2); } @Override public Integer interpret() { return exp1.interpret() - exp2.interpret(); } } ~~~ 计算器,翻译a_+_b_-_c(计算表达式) ~~~ import java.util.Stack; /** * 计算器 * @author lt * */ public class Calculator { private Stack<BaseInterpreter<Integer>> mExpStack = new Stack<BaseInterpreter<Integer>>(); public Calculator(String expression){ // 声明两个BaseInterpreter<Integer>的临时变量,因为计算必须要记录两个数 BaseInterpreter<Integer> exp1,exp2; // 以符号_分隔,这是我们自己规定的 String[] exps = expression.split("_"); for(int i=0;i<exps.length;i++){ switch (exps[i].charAt(0)) { case '+': // 加法 exp1 = mExpStack.pop(); exp2 = new NumberInterpreter(Integer.valueOf(exps[++i])); mExpStack.push(new AdditionInterpreter(exp1, exp2)); break; case '-': exp1 = mExpStack.pop(); exp2 = new NumberInterpreter(Integer.valueOf(exps[++i])); mExpStack.push(new SubtractionInterpreter(exp1, exp2)); break; default: // 数字 mExpStack.push(new NumberInterpreter(Integer.valueOf(exps[i]))); break; } } } /** * 计算 * @return */ public int calculate(){ return mExpStack.pop().interpret(); } } ~~~ 这个类用来翻译a_+_b_-_c等形式结构的语句的意思,这里的符号_是我规定用来分隔数字的,当然你也可以规定其他符号作为分隔符。这个类的方法也很简单,构造方法中先是将表达式按符号_分隔,得到一些运算符和数字,然后在根据分隔出来的字符串的第一个字符的类型判断是+运算符还是-运算符还是数字,如果是+运算符,那么就将存储的上一个数字弹出栈并记录到exp1,然后得到运算符后面的那个字符串(肯定是数字)并记录到变量exp2中,最后用加法解释器解释这两个变量记录的数字并压入栈,等下一次循环的时候弹出和下一个数字进行计算;如果是-运算符,那么和+运算符是一样的,只不过用的是减法解释器;如果是数字,直接压入栈,整个过程是逐步计算的过程。*calculate*方法用来输出计算结果。 测试: ~~~ public class Test { public static void main(String[] args) { String testStr = "1_+_34_-_10_+_50"; Calculator calculator = new Calculator(testStr); System.out.println("result="+calculator.calculate()); } } ~~~ 结果: ![这里写图片描述](https://docs.gechiui.com/gc-content/uploads/sites/kancloud/2016-03-17_56ea5b38086b7.jpg "") 可以看到我们定义的解释器成功解释了a_+_b_-_c这种形式的语句,其实这个解释器实现的功能类似译码的功能或者翻译的功能。不过话说回来,虽然是成功解释了那种形式的语句,但是也只能计算整数的加减法,如果想要做其他运算或者其他类型的数字,如乘除法和浮点型,那么需要添加相应的解释器,但如果涉及到混合运算的时候,那复杂多了,还得考虑优先级,这个时候这种模式可能就不适合了,也就是说解释器模式适合于简单的语句。 总结: 优点: - **灵活的扩展性**。当我们对语法规则扩展延伸适合,只需要添加相应的非终结符解释器(如上面的加减法解释器),并在构建抽象语法树时,使用到新增加的解释器对象(如添加减法解释器)进行具体的解释(计算减法)即可,非常方便。非终结符也就是还没有结束的符号,如下面例子中的加法和减法解释器分别解释的加好和减号一样,它们是二目运算符,其两边肯定要两个数。 缺点: - **类数量膨胀,后期维护困难**。因为对于每一条文法都对应至少一个解释器类,会产生大量的类,导致后期维护困难;同时,对于复杂的文法,构建其抽象的语法树会显得比较繁琐,甚至需要构建多颗语法树,因此,对于复杂的文法并不推荐使用解释器模式。
';