设计模式-解释器模式
设计模式-解释器模式
解释器模式的英文翻译是 Interpreter Design Pattern。它是这样定义的:Interpreter pattern is used to defines a grammatical representation for a language and provides an interpreter to deal with this grammar。翻译成中文就是:解释器模式为某个语言定义它的语法(或者叫文法)表示,并定义一个解释器用来处理这个语法。
简单来说我们可能不懂某些英文的意思,经过翻译解释变成中文,我们就可以理解这个含义了。
从广义上来讲,只要是能承载信息的载体,我们都可以称之为“语言”,比如,古代的结绳记事、盲文、哑语、摩斯密码等。解释器模式主要的组成部分为:语法规则和翻译器,翻译器的作用就是根据语法规则翻译成指定的对象。
案例分析
我定义了一个根据字符串表达式计算数字表达式值的规则,要求传入的字符串是 A+B,例如 “1+2” ,实现一个解释器输出最后的值 3。
public class Main {public static void main(String[] args) {String express = "1+2";String[] split = express.split("\\+");int left = Integer.parseInt(split[0]);int right = Integer.parseInt(split[1]);System.out.println(left + right);}
}
这个案例非常简单,因为我们限制了输入的语法规则,实际上解释器就是这样的思想,传入一个满足所定义语法规则的表达式,解释为最后的对象。
上述的这个案例太简单了,升级一下,传入一个包含基本运算符的后缀表达式,生成最后的结果。
其实这个案例在之前的设计模式中讲过了:设计模式-组合模式,但是它也符合解释器模式的场景,因此这里简化一下实现。
按照规则可以抽象出一个接口
public interface Expression {int getValue();}
接口有五个实现类,分别是 Number、Add、Sub、Multiply 和 Division。其中除了 Number 其他的都需要两个参与者,而且这两个参与者可以是 Expression 的任意实现类。
public class NumberExpression implements Expression{private Integer value;public NumberExpression(Integer value) {this.value = value;}@Overridepublic int getValue() {return value;}}
由于其他的实现类都有两个参与者,因此可以使用抽象类复用创建逻辑
public abstract class BaseExpression implements Expression {protected Expression left;protected Expression right;protected BaseExpression(Expression left, Expression right) {this.left = left;this.right = right;}}
public class AddExpression extends BaseExpression {public AddExpression(Expression left, Expression right) {super(left, right);}@Overridepublic int getValue() {return left.getValue() + right.getValue();}}
省略 Sub、Multiply、Division 等实现
接着我们定义我们的语法规则,由于参数已经是一个合法的后缀表达式了,所以解释步骤如下:
- 从头到尾遍历每一个字符
- 如果是数字,转为 NumberExpression 入栈
- 如果是符号,取出栈顶的两个 Expression 生成符号对应的 Expression 并入栈,例如遇到 + 号,生成一个 AddExpression,左侧表达式为栈的倒数第二个 Expression,右侧表达式为栈的倒数第一个 Expression
- 遍历完成后,栈中只有一个元素,也就是我们最终生成的 Expression 对象
public Expression parse() {LinkedList<Expression> stack = new LinkedList<>();for (String item : suffixExpressions) {if (isOperator(item.charAt(0))) {Expression right = stack.removeLast();switch (item) {case "+": {stack.add(new AddExpression(stack.removeLast(), right));break;}case "-": {stack.add(new SubExpression(stack.removeLast(), right));break;}case "*": {stack.add(new MultiplyExpression(stack.removeLast(), right));break;}case "/": {stack.add(new DivisionExpression(stack.removeLast(), right));break;}}} else {stack.addLast(new NumberExpression(Integer.parseInt(item)));}}return stack.getLast();}
假设输入是
List<String> suffixExpressions = Arrays.asList("1", "3", "4", "2", "-", "2", "3", "6","*", "+", "*", "*", "+", "30", "-");
分析一下它的执行逻辑:
- 遇到 1,封装为 NumberExpression 入栈
- 遇到 3,封装为 NumberExpression 入栈
- 遇到 4,封装为 NumberExpression 入栈
- 遇到 2,封装为 NumberExpression 入栈
- 遇到 -,取出栈的两个元素,封装为 SubExpression 入栈,此时栈的元素为: 1 3 (4-2)
- 遇到 2,封装为 NumberExpression 入栈
- 遇到 3,封装为 NumberExpression 入栈
- 遇到 6,封装为 NumberExpression 入栈
- 遇到 *,取出栈的两个元素,封装为 MultiplyExpression 入栈,此时栈的元素为: 1 3 (4-2) 2 (3*****6)
- 遇到 +,取出栈的两个元素,封装为 AddExpression 入栈,此时栈的元素为: 1 3 (4-2) (2+(3*****6))
- 遇到 *,取出栈的两个元素,封装为 MultiplyExpression 入栈,此时栈的元素为: 1 3 ((4-2)(2+(36)))
- 遇到 ,取出栈的两个元素,封装为 MultiplyExpression 入栈,此时栈的元素为: 1 (3****((4-2)(2+(36))))
- 遇到 +,取出栈的两个元素,封装为 AddExpression 入栈,此时栈的元素为: (1+ (3*****((4-2)(2+(36)))))
- 遇到 30,封装为 NumberExpression 入栈
- 遇到 -,取出栈的两个元素,封装为 SubExpression 入栈,此时栈的元素为: ((1+ (3*****((4-2)(2+(36))))) - 30)
验证:
public class Main {public static void main(String[] args) {List<String> suffixExpressions = Arrays.asList("1", "3", "4", "2", "-", "2", "3", "6", "*", "+", "*", "*", "+", "30", "-");ExpressionParse parse = new ExpressionParse();System.out.println(parse.parse(suffixExpressions).getValue());System.out.println(((1 + (3 * ((4 - 2) * (2 + (3 * 6))))) - 30));}}
解释器模式的代码实现比较灵活,没有固定的模板。应用设计模式主要是应对代码的复杂性,解释器模式也不例外。它的代码实现的核心思想,就是将语法解析的工作拆分到各个小类中,以此来避免大而全的解析类。一般的做法是,将语法规则拆分一些小的独立的单元,然后对每个单元进行解析,最终合并为对整个语法规则的解析。
上述的案例就是把后缀表达式解析为5种具体的表达式并进行合并计算,其中其实还用到了组合模式,例如 AddExpression 的左侧和右侧表达式都可以是任何其他表达式,因此使用 Expression 作为参数类型接收,如果左侧的 Expression 也是 AddExpression 或其他高级表达式,展开来看其实是一个多层的树结构,利用 getValue 获得值的时候也是会先计算子节点的值,类似于树的后续遍历。