行为模式:解释器模式(太难了,不一定正确)
简介
解释器模式:给分析对象定义一个语言,并定义该语言的文法表示,再设计一个解析器来解释语言中的句子。
因为是自定义的语言,所以它的语法表示肯定是有一些相似的地方,比如必须使用空格分割这种要求,或者使用其他特殊的字符。在这个基础上我们才能进行下一步,而不是说随便写个句子就可以的。
举个简单的例子:
我们博客编辑文章语言是markdown,而文法就是md中的各种语法,比如## 我是2级标题
;这个就表示h2标签,这个规则就是文法,我们需要创建一个解释器来解释语法,说白了就是我们需要将各种文法组成的句子转换成html标签。
文法:用于描述语言的语法结构的形式规则
解释器会将解释的语言生成一个抽象的语法树,也称为AST;简称语法树,这个功能再很多地方都有使用,比如webpack,vue的template模板,但是也是最复杂的部分。
语言:
1 + 2 + 3 + 4 - 5
这段计算文本如果被设计为一个语言的话,那么它可以生成如下的语法树:
这个图需要从下往上看,1+2的结果会被+3,依次类推,你会发现这个过程是一个递归调用,而我们的解释器的计算过程也是递归调用。
语法树不是固定格式的,他是根据你设计的语言去生成的,是相互对应的。
如何去创建一个解释器模式?它有几个角色:
- 抽象表达式角色:定义解释器的接口,约定解释器的解释操作,主要包含解释方法 interpret()。
- 终结符表达式角色:是抽象表达式的子类,用来实现文法中与终结符相关的操作,比如上述计算表达式中,每个数值都可以看做是一个终结表达式,因为它不可以再拆分了,是最小的单位。
- 非终结符表达式角色:也是抽象表达式的子类,用来实现文法中与非终结符相关的操作,文法中的每条规则都对应于一个非终结符表达式。它表示的是计算,比如上述计算表达式中的加法、减法,这个处理则是有非终结表达式角色来,所以它是需要接受两个参数的,这个参数可以是终结符表达式角色,也可以是非终结符表达式角色,递归操作就在这里发生。
- 环境角色:通常包含各个解释器需要的数据或是公共的功能,一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- 客户端:主要任务是将需要分析的句子或表达式转换成使用解释器对象描述的抽象语法树,然后调用解释器的解释方法,当然也可以通过环境角色间接访问解释器的解释方法。
代码实现
//计算用的抽象表达式
abstract class Expression {
abstract interpret(): number;
}
//终结符表达式,最小单位
class NumberExpression extends Expression {
private value: number;
constructor(value: number) {
super();
this.value = value;
}
interpret() {
return this.value;
}
}
//非终结表达式:加法
class AdditionExpression extends Expression {
private left: Expression;
private right: Expression;
constructor(left: Expression, right: Expression) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() + this.right.interpret();
}
}
//非终结表达式:减法
class SubtractionExpression extends Expression {
private left: Expression;
private right: Expression;
constructor(left: Expression, right: Expression) {
super();
this.left = left;
this.right = right;
}
interpret() {
return this.left.interpret() - this.right.interpret();
}
}
//环境类,也称为上下文,但实际上时用不着的,因为算法比较简单,就直接在客户端集成了
// class Context {
// private map: Map<Expression, number> = new Map();
// public setValue(expression: Expression, value: number) {
// this.map.set(expression, value);
// }
// public getValue(expression: Expression) {
// return this.map.get(expression);
// }
// }
//客户端,整合上面所有的东西,解释器的入口
// 不会了
class Client {
private list: Array<Expression> = [];
private expression: Expression;
constructor(value: string) {
//根据空格分割成数组
const strArr = value.split(" ");
let left: Expression;
let right: Expression;
for (let i = 0, len = strArr.length; i < len; i++) {
switch (strArr[i]) {
case "+":
left = this.list.pop()!;
right = new NumberExpression(Number(strArr[++i]));
this.list.push(new AdditionExpression(left, right));
break;
case "-":
left = this.list.pop()!;
right = new NumberExpression(Number(strArr[++i]));
this.list.push(new SubtractionExpression(left, right));
break;
default:
this.list.push(new NumberExpression(Number(strArr[i])));
break;
}
}
//根据算式,list中最后一个元素是结果
this.expression = this.list.pop()!;
}
//得到结果
public getResult(): number {
return this.expression.interpret();
}
}
//测试使用
class Test {
constructor() {
const client = new Client("1 + 2 + 3 + 4 - 5");
console.log(client.getResult());
}
}
new Test();
由于计算比较简单,所以不需要单独声明一个环境类,直接在客户端创建一个数组来存取数据就行了。
应用场景
由于使用了递归的方式,所以性能并不是很好。
- 当语言的文法较为简单,且执行效率不是关键问题时。
- 当问题重复出现,且可以用一种简单的语言来进行表达时。
- 当一个语言需要解释执行,并且语言中的句子可以表示为一个抽象语法树的时候,如 XML 文档解释。
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿站点。未经许可,禁止转载。
全部评论 1
杰哥
Google Chrome Windows 10