Interpreter Pattern
1. 什么是解释器模式
解释器模式(Interpreter Pattern):定义一个语言的文法,并且建立一个解释器来解释该语言中的句子,这里的“语言”是指使用规定格式和语法的代码。
解释器模式是一种类行为型模式。它用于描述如何使用面向对象语言构成一个简单的语言解释器。
在某些情况下,为了更好地描述某一些特定类型的问题,我们可以创建一种新的语言,这种语言拥有自己的表达式和结构,即文法规则,这些问题的实例将对应为该语言中的句子。此时,可以使用解释器模式来设计这种新的语言。
在解释器模式中每一个文法规则都将对应一个类,扩展、改变文法以及增加新的文法规则都很方便。
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
抽象语法树:
每个文法规则的语言实例都可以表示为一个抽象语法树(Abstract Syntax Tree, AST),在图中终结符表达式类的实例作为树的叶子节点,而非终结符表达式类的实例作为非叶子节点,它们可以将终结符表达式类的实例以及包含终结符和非终结符实例的子表达式作为其子节。
2. 解释器模式类角色解析
- AbstractExpression(抽象表达式):在抽象表达式中声明了抽象的解释操作,它是所有终结符表达式和非终结符表达式的公共父类。
- TerminalExpression(终结符表达式):终结符表达式是抽象表达式的子类,它实现了与文法中的终结符相关联的解释操作,在句子中的每一个终结符都是该类的一个实例。通常在一个解释器模式中只有少数几个终结符表达式类,它们的实例可以通过非终结符表达式组成较为复杂的句子。
- NonterminalExpression(非终结符表达式):非终结符表达式也是抽象表达式的子类,它实现了文法中非终结符的解释操作,由于在非终结符表达式中可以包含终结符表达式,也可以继续包含非终结符表达式,因此其解释操作一般通过递归的方式来完成。
- Context(环境类):环境类又称为上下文类,它用于存储解释器之外的一些全局信息,通常它临时存储了需要解释的语句。
3. 解释器模式优缺点
解释器是使用面向对象语言构成一个简单的语言解释器,所以它的优缺点都和面向对象有关。
优点:
- 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
- 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
- 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
- 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
缺点:
- 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
- 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
适用场景
- 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
- 一些重复出现的问题可以用一种简单的语言来进行表达。
- 一个语言的文法较为简单。
- 执行效率不是关键问题。
4. 实操感想
解释器模式有固定的流程,即文法规则,按照文法规则一一对应即可。难点应该在于文法规则的整理。
代码过程中,应该优先写Expresion的实现类,即AbstractNode,语法树中的叶子节点,然后再考虑拼装和执行。
每一个终结符、非终结符表达式都对应一个类,但也可使用同一个类表达一些有共性的终结符表达式。
可以思考是不是可以解析成链表的数据结构,顺序解析下去。
具体情况具体分析,非终结符表达式也可以不再包含非终结符表达式,而是将关联操作封装在关联关键字上。
5. 示例
- 解析语句:
up move 5 and down run 10 and left move 5 - 文法规则:
expression ::= direction action distance | composite //表达式
composite ::= expression ‘and’ expression //复合表达式
direction ::= ‘up’ | ‘down’ | ‘left’ | ‘right’ //移动方向
action ::= ‘move’ | ‘run’ //移动方式
distance ::= an integer //移动距离
// 抽象表达式
abstract class AbstractNode {
public abstract String interpret();
}
// 终结符表达式,direction
class DirectionNode extends AbstractNode {
private String direction;
DirectionNode(String direction) {
this.direction = direction;
}
//方向表达式的解释操作
@Override
public String interpret() {
if (direction.equalsIgnoreCase("up")) {
return "向上";
} else if (direction.equalsIgnoreCase("down")) {
return "向下";
} else if (direction.equalsIgnoreCase("left")) {
return "向左";
} else if (direction.equalsIgnoreCase("right")) {
return "向右";
} else {
return "无效指令";
}
}
}
// 终结符表达式 action
class ActionNode extends AbstractNode {
private String action;
ActionNode(String action) {
this.action = action;
}
//动作(移动方式)表达式的解释操作
@Override
public String interpret() {
if (action.equalsIgnoreCase("move")) {
return "移动";
} else if (action.equalsIgnoreCase("run")) {
return "快速移动";
} else {
return "无效指令";
}
}
}
// 终结符表达式,distance
class DistanceNode extends AbstractNode {
private String distance;
DistanceNode(String distance) {
this.distance = distance;
}
//距离表达式的解释操作
@Override
public String interpret() {
return this.distance;
}
}
// 非终结符表达式,完整的句子
class SentenceNode extends AbstractNode {
private AbstractNode direction;
private AbstractNode action;
private AbstractNode distance;
SentenceNode(AbstractNode direction, AbstractNode action, AbstractNode distance) {
this.direction = direction;
this.action = action;
this.distance = distance;
}
//简单句子的解释操作
@Override
public String interpret() {
return direction.interpret() + action.interpret() + distance.interpret();
}
}
// 非终结符表达式,链表结构
class AndNode extends AbstractNode {
private AbstractNode left; //And的左表达式
private AbstractNode right; //And的右表达式
// 链表
AndNode(AbstractNode left, AbstractNode right) {
this.left = left;
this.right = right;
}
//And表达式解释操作
@Override
public String interpret() {
return left.interpret() + "再" + right.interpret();
}
}
// 解析类,解析语句,形成链表
// ...
void handle(String instruction) {
AbstractNode left = null, right = null;
AbstractNode direction = null, action = null, distance = null;
Stack<AbstractNode> stack = new Stack<>(); //声明一个栈对象用于存储抽象语法树
String[] words = instruction.split(" "); //以空格分隔指令字符串
for (int i = 0; i < words.length; i++) {
if (words[i].equalsIgnoreCase("and")) {
left = stack.pop(); //弹出栈顶表达式作为左表达式
String word1 = words[++i];
direction = new DirectionNode(word1);
String word2 = words[++i];
action = new ActionNode(word2);
String word3 = words[++i];
distance = new DistanceNode(word3);
right = new SentenceNode(direction, action, distance); //右表达式
stack.push(new AndNode(left, right)); //将新表达式压入栈中
}
//如果是从头开始进行解释,则将前三个单词组成一个简单句子SentenceNode并将该句子压入栈中
else {
String word1 = words[i];
direction = new DirectionNode(word1);
String word2 = words[++i];
action = new ActionNode(word2);
String word3 = words[++i];
distance = new DistanceNode(word3);
left = new SentenceNode(direction, action, distance);
stack.push(left); //将新表达式压入栈中
}
}
this.node = stack.pop();
this.node.interpret(); // 从链表头开始解析
}