说明
解释器模式(interpreter),给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
UML
角色:
抽象表达式(AbstractExpression)角色:约定解释器的解释操作,主要是一个interpret()方法。
终结符表达式(TerminalExpression)角色:用来实现文法中和终结符相关的解释操作,不再包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。
非终结符表达式(NonterminalExpression)角色:用来实现文法中和非终结符相关的解释操作,通常一个解释器对应一个语法规则,可以包含其它的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的组合对象,可以有多种非终结符解释器。
环境(Context)角色:也称“上下文”,常用HashMap来代替,通常包含解释器之外的一些全局信息(解释器需要的数据,或是公共的功能)。
客户端(Client)角色:构建文法表示的抽象语法树(Abstract Syntax Tree,该抽象语法树由终结符表达式和非终结符表达式的实例装配而成),并调用解释操作interpret()方法。
代码
我在之前设计模式文章中的很多例子都是参考的《大话设计模式》这本书中的例子来写的,但这次感觉书中写的多少有些复杂了,下面这篇博客的例子感觉更易懂,并且我参考这个例子又写了一遍,感觉理解起来还是挺有帮助的。
下面例子参考的原博地址,点击访问。
一个建议加减法计算器的例子,代码如下。
抽象表达式
/**
* 抽象表达式
*
* @author ctl
* @date 2021/2/1
*/
public interface AbstractExpression {
// 用map来代替context
int interpret(Map<String, Integer> var);
}
终结符表达式
/**
* 终结符表达式,代表参加运算的元素对象
*
* @author ctl
* @date 2021/2/1
*/
public class VarExpression implements AbstractExpression {
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
public int interpret(Map<String, Integer> var) {
return var.get(key);
}
}
非终结符表达式
/**
* 非终结符表达式,运算符(此处为加法和减法)的抽象父类,真正的解释操作由其子类来实现
*
* @author ctl
* @date 2021/2/1
*/
public abstract class SymbolExpression implements AbstractExpression {
protected AbstractExpression left;
protected AbstractExpression right;
// 非终结符表达式的解释操作只关心自己左右两个表达式的结果
public SymbolExpression(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
}
加法表达式
/**
* 加法表达式
*
* @author ctl
* @date 2021/2/1
*/
public class AddExpression extends SymbolExpression {
public AddExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
@Override
public int interpret(Map<String, Integer> var) {
return super.left.interpret(var) + super.right.interpret(var);
}
}
减法表达式
/**
* 减法表达式
*
* @author ctl
* @date 2021/2/1
*/
public class SubExpression extends SymbolExpression {
public SubExpression(AbstractExpression left, AbstractExpression right) {
super(left, right);
}
@Override
public int interpret(Map<String, Integer> var) {
return super.left.interpret(var) + super.right.interpret(var);
}
}
计算器
/**
* @author ctl
* @date 2021/2/1
*/
public class Calculator {
private AbstractExpression expression;
/**
* 对公式进行解析操作,构件语法树,+ -这种操作符是树中的枝干,具体的数字是树的叶子节点
*
* @param expStr 输入的公式
*/
public Calculator(String expStr) {
// 定义一个栈,安排运算的先后顺序
Stack<AbstractExpression> stack = new Stack<>();
// 表达式拆分为字符数组
char[] charArray = expStr.toCharArray();
// 运算
AbstractExpression left;
AbstractExpression right;
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 calculate(Map<String, Integer> var) {
return this.expression.interpret(var);
}
}
测试类
/**
*
* @author ctl
* @date 2021/2/1
*/
public class InterpreterMain {
public static void main(String[] args) {
// 这里使用map来当作context的角色
Map<String, Integer> ctx = new HashMap<>();
ctx.put("a", 10);
ctx.put("b", 20);
ctx.put("c", 30);
ctx.put("d", 40);
ctx.put("e", 50);
ctx.put("f", 60);
Calculator calc;
calc = new Calculator("a+b-c");
int result = calc.calculate(ctx);
System.out.println("Result of a+b-c: " + result);
calc = new Calculator("d-a-b+c");
result = calc.calculate(ctx);
System.out.println("Result of d-a-b+c: " + result);
}
}
结果
总结
当有一个语言需要解释执行,并且可以将该语言中的句子表示为一个抽象语法树时,可以使用解释器模式。如sql解析,编程语言的解析等等。
解释器模式可以容易地改变和扩展文法,因为该模式使用类来表示文法规则,可以使用继承继承来改变或扩展该文法。也比较容易实现文法,因为定义抽象语法树的各个节点的类的实现大体相似,这些类都易于直接编写。
当然也有不足之处,解释器模式为文法中的每一条规则都至少定义了一个类,因此包含很多规则的文法可能难以维护和管理。