介绍
一种用得比较少的行为型模式。其提供一种解释语言的语法或表达式的方式,化繁为简。比如给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器用来解释语言中的句子。Android中的AndroidManifest.xml的解析,PackageParser就用到了这种模式。
场景
- 简单语言需要解释执行且可以将该语言的语句表示为抽象语法树(如±法运算)
- 特定领域出现不断重复,可以将该问题转换为一种语法规则。
UML
- AbstractExpression: 抽象表达式,生命一个抽象的解释操作父类,并定义一个抽象的解释方法,其具体的实现在子类解释器完成。
- TerminalExpression:终结符表达式。实现文法终与终结符有关的解释操作。文法中每一个终结符都有一个具体的终结表达式与之对应,
- NonterminalExpression: 非终结符表达式。实现文法中与非终结符有关的解释操作。
- Context:上下文环境类。
- Client:客户类。
事例
举一个简单的例子,比如"3 + 5 + 7",构建一个语法来进行解析和计算。
这里,咱们将3,5,7可看为终结符,+号可看为非终结符
- 先建立一个抽象的解析类
/**
* 抽象解析类
*/
public abstract class AbsPrease {
/**
* 抽象解析方法
* 具体解析由子类实现
*/
public abstract int interpret();
}
- 创建数字的解析(终结符)
/**
* 数字解析
*/
public class NumPrease extends AbsPrease {
/**
* 承载的数字
*/
private int num;
public NumPrease(int num) {
this.num = num;
}
/**
* 数字的解析类直接返回数字
*
* @return
*/
@Override
public int interpret() {
System.out.println("数字解析:num="+num);
return num;
}
}
数字的解析类,就包含一个数字,计算的时候就返回数字即可。
- 创建一个抽象的操作符解析(非终结符),之所以是抽象,目的是后面还可以扩展成其他操作符
/**
* 操作符解析类,还是用抽象类,后续可以再继承,实现不同操作
*/
public abstract class OperatPrease extends AbsPrease {
/**
* 操作符左侧的解析
*/
protected AbsPrease numLeft;
/**
* 操作符右侧解析类
*/
protected AbsPrease numRight;
public OperatPrease(AbsPrease numLeft, AbsPrease numRight) {
this.numLeft = numLeft;
this.numRight = numRight;
}
}
这里的持有运算符两边解析器
- 创建加法解析器继承自操作符解析器
/**
* 加法操作符实现类,继承操作解析类
*/
public class AddOpeartionPrease extends OperatPrease {
/**
* 传入左右数字
*
* @param numLeft
* @param numRight
*/
public AddOpeartionPrease(AbsPrease numLeft, AbsPrease numRight) {
super(numLeft, numRight);
}
@Override
public int interpret() {
//返回两个数字相加
System.out.println("加法解析数字解析");
return numLeft.interpret() + numRight.interpret();
}
}
顾名思义,计算左右解析的相加
- 定义一个承载业务的对象
/**
* 计算类,用于承载一些业务
*/
public class CalculatePrase {
//存储相关解析器,在这里至始至终里面都只存了一个解析器(存了取,取了存)
private Stack<AbsPrease> stack = new Stack<>();
public CalculatePrase() {
}
/**
* 解析计算字符串,并返回计算后的结构
*
* @param expression 字符串 如 "1 + 2 + 3"
* @return 计算后的结果
*/
public int interpret(String expression) {
/*定义两个临时变量*/
AbsPrease absPrease1;
AbsPrease absPrease2;
//以空格将文字分开,得到数字和运算符
String[] elements = expression.split(" ");
for (int i = 0; i < elements.length; i++) {
switch (elements[i]) {
//加号
case "+":
//将当前存储的解析给拿出来
absPrease1 = stack.pop();
//获取到加号之后的一个解析
absPrease2 = new NumPrease(Integer.valueOf(elements[++i]));
//将之前存储的数字与当前加号后的一个数字再放入栈中
stack.push(new AddOpeartionPrease(absPrease1, absPrease2));
break;
//数字
default:
//数字直接入栈
stack.push(new NumPrease(Integer.valueOf(elements[i])));
break;
}
}
//前面解析完成之后,再进行一次性计算打印
return stack.pop().interpret();
}
}
其接收字符串,并通过解析来得到最终结果。
- 测试
System.out.println("计算结果:" + new CalculatePrase().interpret("1 + 2 + 5"));
- 输出:
加法解析数字解析
加法解析数字解析
数字解析:num=1
数字解析:num=2
数字解析:num=5
计算结果:8
为什么这么输出,最后栈中的唯一一个AbsPrase为如下,我想大家已经懂了,如果再多,就是不断嵌套:
在这里解释器的优势也出来了,好扩展,咱们可以再扩展减法的,只需要创建一个减法的解释,然后再改一下CalculatePrase中的case即可
优缺点
优点:
- 灵活的扩展性,需要对文法进行扩展对时候,只需要加响应对非终结符解释器,在构件抽象语法树时候,使用新增对解释器对象进行具体对解释即可
缺点:
- 每一条文法都对应一个解释器,会生成大量对类,后期维护困难。
- 复杂文法就不适合了,构建很困难。
总结:其思想就是通过固定的解释语法来将一个约定好语法的整体(比如这里的运算字符串,其他如xml中的内容)解析出我们想要的理解的结果,构建一个这个确实是比较复杂,平时几乎也不会用到。