设计模式之解释器模式
解释器模式:
解释器模式(Interpreter Pattern) 是指给定一门语言, 定义它的语法的一种表示, 并定义一个解释器,该解释器使用该表示来解释语言中的句子。是一种按照规定的语法进行解析的模式,属于行为型模式。
就比如编译器可以将源码编译解释为机器码, 让CPU能进行识别并运行。解释器模式的作用其实与编译器一样,都是将一些固定的语法进行解释,构建出一个解释句子的解释器。简单理解,解释器是一个简单语法分析工具,它可以识别句子语义,分离终结符号和非
终结符号,提取出需要的信息,让我们能针对不同的信息做出相应的处理。其核心思想是识别语法,构建解释。
解释器模式的应用场景:
其实我们每天都生活在解释器模式中,我们平时所听到的音乐都可以通过简谱记录下来;还有战争年代发明的摩尔斯电码(又译为摩斯密码, Morsecode) , 其实也是一种解释器。我们的程序中,如果存在一种特定类型的问题,该类型问题涉及多个不同实例,但是具备固定语法描述,那么可以使用解释器模式对该类型问题进行解释,分离出需要的信息,根据获取的信息做出相应的处理。简而言之,对于一些固定语法构建一个解释句子的解释器。解释器模式适用于以下应用场景:
- 一些重复出现的问题可以用一种简单的语言来进行表达;
- 一个简单语法需要解释的场景。
解释器模式主要包含四种角色:
- 抽象表达式(Expression) :负责定义一个解释方法interpret, 交由具体子类进行具体解释;
- 终结符表达式(Terminal Expression) :实现文法中与终结符有关的解释操作。文法中的每一个终结符都有一个具体终结表达式与之相对应,比如公式R=R1+R2,R1和R2就是终结符,对应的解析R1和R2的解释器就是终结符表达式。通常一个解释器模式中只有一个终结符表达式,但有多个实例,对应不同的终结符(R1,R2);
- 非终结符表达式(Nonterminal Expression) :实现文法中与非终结符有关的解释操作。文法中的每条规则都对应于一个非终结符表达式。非终结符表达式一般是文法中的运算符或者其他关键字,比如公式R=R1+R2中, “+”就是非终结符,解析“+”的解释器就是一个非终结符表达式。非终结符表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式;
- 上下文环境类(Context) :包含解释器之外的全局信息。它的任务一般是用来存放文法中各个终结符所对应的具体值,比如R=R1+R2,给R1赋值100,给R2赋值200,这些信息需要存放到环境中。
使用解释器模式解析数学表达式:
下面我们用解释器模式来实现一个数学表达式计算器,包含加减乘除运算。首先定义抽象表达式角色IArithmeticInterpreter接口:
public interface IArithmeticInterpreter {
int interpret();
}
创建终结表达式角色AbstractInterpreter抽象类:
public abstract class AbstractInterpreter implements IArithmeticInterpreter {
protected IArithmeticInterpreter left;
protected IArithmeticInterpreter right;
public AbstractInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
this.left = left;
this.right = right;
}
}
分别创建非终结表达式角色加、减、乘、除解释器:
//加
public class AddInterpreter extends AbstractInterpreter {
public AddInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
super(left, right);
}
public int interpret() {
return this.left.interpret() + this.right.interpret();
}
}
//减
public class SubInterpreter extends AbstractInterpreter {
public SubInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right) {
super(left, right);
}
public int interpret() {
return this.left.interpret() - this.right.interpret();
}
}
//乘
public class MultiInterpreter extends AbstractInterpreter {
public MultiInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
super(left,right);
}
public int interpret() {
return this.left.interpret() * this.right.interpret();
}
}
//除
public class DivInterpreter extends AbstractInterpreter {
public DivInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right){
super(left,right);
}
public int interpret() {
return this.left.interpret() / this.right.interpret();
}
}
创建数字表达式类:
public class NumInterpreter implements IArithmeticInterpreter {
private int value;
public NumInterpreter(int value) {
this.value = value;
}
public int interpret() {
return this.value;
}
}
创建计算器类:不在乎乘除与加减得顺序,这都是解释器按照语义得解释,本案例中按照顺序执行
public class Calculator {
private Stack<IArithmeticInterpreter> stack = new Stack<IArithmeticInterpreter>();
public Calculator(String expression) {
this.parse(expression);
}
private void parse(String expression) {
String [] elements = expression.split(" ");
IArithmeticInterpreter leftExpr, rightExpr;
for (int i = 0; i < elements.length ; i++) {
String operator = elements[i];
if (OperatorUtil.isOperator(operator)){
leftExpr = this.stack.pop();
rightExpr = new NumInterpreter(Integer.valueOf(elements[++i]));
System.out.println("出栈: " + leftExpr.interpret() + " 和 " + rightExpr.interpret());
this.stack.push(OperatorUtil.getInterpreter(leftExpr, rightExpr,operator));
System.out.println("应用运算符: " + operator);
}
else{
NumInterpreter numInterpreter = new NumInterpreter(Integer.valueOf(elements[i]));
this.stack.push(numInterpreter);
System.out.println("入栈: " + numInterpreter.interpret());
}
}
}
public int calculate() {
return this.stack.pop().interpret();
}
}
操作工具类:
public class OperatorUtil {
public static boolean isOperator(String symbol) {
return (symbol.equals("+") || symbol.equals("-") || symbol.equals("*"));
}
public static AbstractInterpreter getInterpreter(IArithmeticInterpreter left, IArithmeticInterpreter right, String symbol) {
if (symbol.equals("+")) {
return new AddInterpreter(left, right);
} else if (symbol.equals("-")) {
return new SubInterpreter(left, right);
} else if (symbol.equals("*")) {
return new MultiInterpreter(left, right);
} else if (symbol.equals("/")) {
return new DivInterpreter(left, right);
}
return null;
}
}
测试类:
public class Test {
public static void main(String[] args) {
System.out.println("result: " + new Calculator("10 + 30").calculate());
System.out.println("result: " + new Calculator("10 + 30 - 20").calculate());
System.out.println("result: " + new Calculator("100 * 2 + 400 * 1 + 66").calculate());
}
}
优点:
- 扩展性强:在解释器模式中由于语法是由很多类表示的,当语法规则更改时,只需修改相应的非终结符表达式即可;若扩展语法时,只需添加相应非终结符类即可;
- 增加了新的解释表达式的方式;
- 易于实现文法:解释器模式对应的文法应当是比较简单且易于实现的,过于复杂的语法并不适合使用解释器模式。
缺点:
- 语法规则较复杂时,会引起类膨胀:解释器模式每个语法都要产生一个非终结符表达式当语法规则比较复杂时,就会产生大量的解释类,增加系统维护困难;
- 执行效率比较低:解释器模式采用递归调用方法,每个非终结符表达式只关心与自己有关的表达式,每个表达式需要知道最终的结果,因此完整表达式的最终结果是通过从后往前递归调用的方式获取得到。当完整表达式层级较深时,解释效率下降,且出错时调试困难,因为递归迭代层级太深。