初探解释器模式

代码链接

koonchen/design-patterns/interpreter

要点

给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。

例如现在有一个正则表达式,其中有以下各个符号:

cover

其中,符号 expression 是开始符号,literal 是字的终结符。

解释器使用类来表示每一条文法,它的右边的符号是实例,看上图,根据这些,我们需要五个语法类:

cover

假设现在有一个正则表达:

1
raining & (dogs | cats) *

它对应的抽象语法树是:

cover

解释器模式的适用性:

  • 文法的类层次庞大而且无法管理时。
  • 效率不是一个关键问题,最搞笑的解释器通常不是通过直接解释语法分析树实现的,而是转换成另一个形式,这种情况下,解释器仍可以用解释器模式来实现。

解释器模式的参与者:

  • AbstractExpression — 声明一个抽象的解释操作,这个接口为抽象语法树中所有节点所共享
  • TerminalExpression — 实现与文法中的终结符相关联的解释操作
  • NonterminalExpresson — 对每一条规则进行解释操作,一般用递归表示
  • Context — 包含解释器之外的一些全局信息
  • Client — 调用解释操作

解释器模式的结构:

cover

解释器模式的优缺点:

  • 易于改变和扩展文法
  • 易于实现文法
  • 增加新的解释器表达的方式
  • 复杂的文法难以维护

实现

在实现前有一些需要考虑的问题:

  • 抽象语法树:解释器模式并未监视如何创建一个抽象的语法树
  • 定义解释操作:并不一定要在表达式中定义解释操作
  • Flyweight 模式共享终结符:一个句子中可能出现多次同一个终结符,最好共享这个符号的单个拷贝,这个节点通常不存储关于它在语法树中的位置信息,任何它们需要的信息都是父节点传递的,存在内部状态和外部状态,所以需要使用 Flyweight

Java 实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.util.ArrayList;
import java.util.List;

class Context {
// 存一些全局信息
private String name = "Context";

public String getName() {
return name;
}
}

interface AbstractExpression {
void interpret(Context context);
}

class TerminalExpression implements AbstractExpression {

@Override
public void interpret(Context context) {
System.out.println("TerminalExpression instance has been created");
}
}

class NonterminalExpression implements AbstractExpression {

private List<AbstractExpression> list = null;
private String name;

public NonterminalExpression(String name) {
this.name = name;
}

public void setList(List<AbstractExpression> list) {
this.list = new ArrayList<>();
for (AbstractExpression expression: list) {
this.list.add(expression);
}
}

@Override
public void interpret(Context context) {
if (this.list != null) {
for (AbstractExpression expression: this.list) {
expression.interpret(context);
}
}
System.out.println(this.name + " instance has been created");
}
}

public class InterpreterClient {
public static void main(String[] args) {
Context context = new Context();
AbstractExpression aRepetition = new NonterminalExpression("repetition");
AbstractExpression dogsLiteral = new NonterminalExpression("dogs");
AbstractExpression catsLiteral = new NonterminalExpression("cats");
List<AbstractExpression> list = new ArrayList<>();
list.add(dogsLiteral);
list.add(catsLiteral);
((NonterminalExpression) aRepetition).setList(list);
aRepetition.interpret(context);
}
}

这里的 NonterminalExpression 实现一个重复表达式的操作,而 ContextTerminalExpression 并没有实际作用,但还是能看出这个模式的合理性。

相关模式

  • Composite 模式:抽象语法树是一个复合模式的实例
  • Flyweight 模式:说明了如何在抽象语法树中共享终结符
  • Iterator 模式:解释器可用一个迭代器遍历该结构
  • Visitor 模式:可用来在一个类中维护抽象语法树中各个节点的行为