一、解释器模式
解释器模式(Interpreter Pattern)提供了评估语言的语法或表达式的方式,它属于行为型模式。这种模式实现了一个表达式接口,该接口解释一个特定的上下文。这种模式被用在 SQL 解析、符号处理引擎等。
二、介绍
意图:给定一个语言,定义它的文法表示,并定义一个解释器,这个解释器使用该标识来解释语言中的句子。
主要解决:对于一些固定文法构建一个解释句子的解释器。
何时使用:如果一种特定类型的问题发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。
如何解决:构建语法树,定义终结符与非终结符。
关键代码:构建环境类,包含解释器之外的一些全局信息,一般是 HashMap。
应用实例:编译器、运算表达式计算。
优点: 1、可扩展性比较好,灵活。 2、增加了新的解释表达式的方式。 3、易于实现简单文法。
缺点: 1、可利用场景比较少。 2、对于复杂的文法比较难维护。 3、解释器模式会引起类膨胀。 4、解释器模式采用递归调用方法。
使用场景: 1、可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。 2、一些重复出现的问题可以用一种简单的语言来进行表达。 3、一个简单语法需要解释的场景。
注意事项:可利用场景比较少,JAVA 中如果碰到可以用 expression4J 代替。
三、结构
解释器模式包含以下主要角色:
1、抽象表达式角色:定义解释器的几口,约定解释器的解释操作,主要包含解释方法interpret()
2、终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,文法中的每条规则都有一个具体终结表达式与之相对应。
3、非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对一个于一个非终结符表达式
4、环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
5、客户端:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色简介访问解释器的解释方法。
四、代码实现
定义一个抽象表达式类
package IP_01_Interpreter;
/**
* @Author: {LZG}
* @ClassName: AbstractExpression
* @Description: 抽象表达式类
* @Date: 2022/4/6 19:18
**/
public abstract class AbstractExpression {
public abstract int interpret(Context context);
}
封装变量类
package IP_01_Interpreter;
/**
* @Author: {LZG}
* @ClassName: Variable
* @Description: 封装变量地类
* @Date: 2022/4/6 19:19
**/
public class Variable extends AbstractExpression{
// 声明存储变量名的成员
private String name;
public Variable(String name) {
this.name = name;
}
@Override
public int interpret(Context context) {
// 直接返回变量的值
return context.getValue(this);
}
@Override
public String toString() {
return name;
}
}
生成加法和减法的表达式类
package IP_01_Interpreter;
/**
* @Author: {LZG}
* @ClassName: Plus
* @Description: 加法表达式类
* @Date: 2022/4/6 19:25
**/
public class Plus extends AbstractExpression{
// +号左边的表达式
private AbstractExpression left;
// +号右边的表达式
private AbstractExpression right;
public Plus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果和右边的表达式结果进行相加
return left.interpret(context)+right.interpret(context);
}
@Override
public String toString() {
return "("+left.toString()+"+"+right.toString()+")";
}
}
package IP_01_Interpreter;
/**
* @Author: {LZG}
* @ClassName: Plus
* @Description: 减法表达式类
* @Date: 2022/4/6 19:25
**/
public class Minus extends AbstractExpression{
// -号左边的表达式
private AbstractExpression left;
// -号右边的表达式
private AbstractExpression right;
public Minus(AbstractExpression left, AbstractExpression right) {
this.left = left;
this.right = right;
}
@Override
public int interpret(Context context) {
// 将左边表达式的结果和右边的表达式结果进行相加
return left.interpret(context)-right.interpret(context);
}
@Override
public String toString() {
return "("+left.toString()+"-"+right.toString()+")";
}
}
生成环境角色类
package IP_01_Interpreter;
import java.util.HashMap;
import java.util.Map;
/**
* @Author: {LZG}
* @ClassName: Context
* @Description: 环境角色类
* @Date: 2022/4/6 19:19
**/
public class Context {
// 定义一个map集合用来存储变量以及一个对应的值
private Map<Variable,Integer> map=new HashMap<>();
// 添加变量的功能
public void assign(Variable variable,Integer value){
map.put(variable,value);
}
// 根据变量获取值
public int getValue(Variable variable){
return map.get(variable);
}
}
客户
package IP_01_Interpreter;
/**
* @Author: {LZG}
* @ClassName: Client
* @Description: TODO
* @Date: 2022/4/6 19:51
**/
public class Client {
public static void main(String[] args) {
// 创建环境变量
Context context = new Context();
// 创建多个变量
Variable a = new Variable("a");
Variable b = new Variable("b");
Variable c = new Variable("c");
Variable d = new Variable("d");
// 将环境变量存储到环境对象中
context.assign(a,1);
context.assign(b,2);
context.assign(c,3);
context.assign(d,4);
// 获取抽象语法树 a+b-c+d
AbstractExpression expression=new Minus(a,new Plus(new Minus(b,c),d));
// 解释 计算
int result = expression.interpret(context);
System.out.println(expression+"="+result);
}
}
测试结果
类图
五、优缺点
优点:
1、易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。没一条文法规则都可以表示一个类,因此可以方便地实现一个简单的语言
2、实现文法较为容易。在抽象语法树中每一个表达式节点类地实现方式都是相似地,这些类地代码编写都不会特别复杂。
3、增加新的解释表达式较为方便。如果用户需要增加新的表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无需修改,符合开闭原则。
缺点:
1、对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类地个数将会急剧增加,导致系统难以管理和维护
2、执行效率较低。由于解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码过程地调试过程也比较麻烦。