概述
基本介绍
1、解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的语言是指使用规定的格式和语法的代码。解释器模式是一种行为型模式。
2、解释器模式用于描述如何使用面向对象语言构成一个简单的语言解释器,在某些特定的情况下,为了更好的描述一类特定类型的问题,比如正则表达式,可以创建一种新的语言,这种语言拥有自己的表达式结构,也就是语法规则,使用解释器模式来设计这种新的语言,解释器模式是一种使用频率相对较低但学习难度较大的设计模式。
3、应用场景:应用可以将一个需要解释执行的语言中的句子来表示为一个抽象语法树;一些重复出现的问题可以使用一种简单的语言来表达;一个简单的语法需要解释的场景。
原理类图
类图角色说明
1、Context:环境类,也称为上下文类,它用于存储解释器之前一些全局信息,通常情况下也会临时存储需要解释的语句。
2、AbstractExpression:抽象表达式,在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。声明一个抽象的解释操作,这个方法为抽象语法树中所有的节点所共享。
3、TerminalExpression:终结符表达式,是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。实现文法中的终结符相关的解释操作。
4、NonTerminalExpression:非终结符表达式,也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。为文法中的非终结符实现解释操作。
5、输入Context和TerminalExpression通过Client输入即可。
案例
需求使用解释器模式实现计算a+b-c的值
需求类图解析
代码实现
package com.example.pattern.interpreter;
import lombok.Getter;
import lombok.Setter;
import java.util.HashMap;
import java.util.Stack;
/**
* 解释器模式 实现 a+b-c 的计算
*
* @author zjt
* @date 2021-01-06
*/
// 抽象表达式 通过HashMap的键值对可以获取到变量的值
public interface Expression {
// 解释公式和数值
// key相当于标表达式 a,b,c value就是具体的值
// 类似于 {a=10,b=20}
int interpreter(HashMap<String, Integer> var);
}
// 变量的解释器
class VarExpression implements Expression {
// key 就是 key=a key=b key=c
private String key;
public VarExpression(String key) {
this.key = key;
}
@Override
// var 类似于 {a=10,b=20}
// 根据变量名称返回对应的值
public int interpreter(HashMap<String, Integer> var) {
return var.get(this.key);
}
}
@Getter
@Setter
// 抽象运算符号解析器,每个运算符号都只和自己左右两个数字有关系,
// 但是左右两个数组有可能也是一个解析的结果,无论何种类型,都是Expression的实现类
abstract class SymbolExpression implements Expression {
protected Expression left; // 左表达式
protected Expression right; // 右表达式
public SymbolExpression(Expression left, Expression right) {
this.left = left;
this.right = right;
}
@Override
// SymbolExpression 本质上需要子类去实现 因此该方法可以不实现
// 也可以默认实现一下
public abstract int interpreter(HashMap<String, Integer> var);
}
// 加号解释器
class AddExpression extends SymbolExpression {
public AddExpression(Expression left, Expression right) {
super(left, right);
}
@Override
public int interpreter(HashMap<String, Integer> var) {
// super.left.interpreter(var) 返回left表达式对应的值
// super.right.interpreter(var) 返回的是right表达式对应的值
return super.left.interpreter(var) + super.right.interpreter(var);
}
}
// 减号解释器
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);
}
}
class Calculator {
// 定义表达式
private Expression expression;
// 构造函数传参 并解析出表达式
public Calculator(String expString) {
this.analysisExpression(expString);
}
// 根据字符串 解析出表达式
private void analysisExpression(String str) {
// 根据栈的特性 排出运算的 先后顺序
Stack<Expression> expressionStack = new Stack<>();
Expression left; // 左表达式
Expression right; // 右表达式
// 将字符串转成 Char 数组
char[] chars = str.toCharArray();
for (int i = 0; i < chars.length; i++) {
switch (chars[i]) {
case '+':
left = expressionStack.pop(); // 从栈中取出 left 加号表达时
right = new VarExpression(String.valueOf(chars[++i])); // 取出右表达式
expressionStack.push(new AddExpression(left, right)); // 构建的AddExpression对象,加入栈中
break;
case '-':
left = expressionStack.pop();
right = new VarExpression(String.valueOf(chars[++i]));
expressionStack.push(new SubExpression(left, right));
break;
default:
// 如果是变量 就创建一个VarExpression对象并push到栈中
expressionStack.push(new VarExpression(String.valueOf(chars[i])));
break;
}
}
// 当遍历完整个 CharArray数组 就得到最后的Expression
this.expression = expressionStack.pop();
}
public int run(HashMap<String, Integer> var) {
// 最后将表达式和var传递给expression.interpreter 方法解释执行
return this.expression.interpreter(var);
}
}
class Client {
public static void main(String[] args) {
String expStr = "a+b-c+d";
HashMap<String, Integer> var = new HashMap<String, Integer>() {{
put("a", 10);
put("b", 5);
put("c", 7);
put("d", 10);
}};
Calculator calculator = new Calculator(expStr);
System.out.println("计算结果:" + expStr + "=" + calculator.run(var));
}
}
运行表达式debug截图
根据上面的截图发现,表达式被解析为类似于下图描述的抽象语法树。
总结
优点
1、易于改变和扩展语法。由于在解释器模式中使用类来表示语言的语法规则,因此可以通过继承等机制来改变或扩展语法。
2、每一条语法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
3、实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
4、增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
缺点
1、对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
2、执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。