5.3 INTERPRETER(解释器)—类行为型模式

1 意图

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

2 动机

如果一种特定类型的问题发生的频率足够高,那么可能就值得将问题的各个实例表述为一个简单语言中的句子。这样就可以构建一个解释器,该解释器通过解释这些句子来解决该问题。

例如,搜索匹配一个模式的字符串是一个常见的问题。正则表达式是描述字符串模式的一种标准语言。与其为每一个的模式都构造一个特定的算法,不如使用一种通用的搜索算法来解释执行一个正则表达式,该正则表达式定义了待匹配字符串的集合。

解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。在上面的例子中,本设计模式描述了如何为正则表达式定义一个文法,如果表示一个特定的正则表达式,以及如何解释这个正则表达式。
考虑以下文法定义正则表达式:


image.png

解释器模式使用类来表示每一条文法规则,在规则右边的符号是这些类的实例变量。上面的文法用5个类表示,一个抽象类RegularExpression和它的四个子类LiteralExpression、AlternationExpression、SequenceExpression和RepetitionExpression后三个类定义的变量代表子表达式。


image.png

每个用这个文法定义的正则表达式都被表示为一个由这些类的实例构成的抽象语法树。
例如, 抽象语法树:
image.png

表示正则表达式:
raining & (dogs | cats) *
如果我们为RegularExpression的每一子类都定义解释 ( Interpret )操作,那么就得到了为这些正则表达式的一个解释器。解释器将该表达式的上下文做为一个参数。上下文包含输入字符串和关于目前它已有多少已经被匹配等信息。为匹配输入字符串的下一部分,每一RegularExpression的子类都在当前上下文的基础上实现解释操作 (Interpret)。例如,

  • LiteralExpression将检查输入是否匹配它定义的字 ( literal );
  • AlternationExpression将检查输入是否匹配它的任意一个选择项;
  • RepetitionExpression将检查输入是否含有多个它所重复的表达式。
3 适用性

当有一个语言需要解释执行 , 并且你可将该语言中的句子表示为一个抽象语法树时,可使
用解释器模式。而当存在以下情况时该模式效果最好:

  • 该文法简单对于复杂的文法 , 文法的类层次变得庞大而无法管理。此时语法分析程序生
    成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式 , 这样可以节
    省空间而且还可能节省时间。
  • 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的 , 而是
    首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种
    情况下, 转换器仍可用解释器模式实现, 该模式仍是有用的。
4 结构
image.png
5 参与者
  • AbstractExpression(抽象表达式,如RegularExpression )
    ——声明一个抽象的解释操作,这个接口为抽象语法中所有的节点所共享;
  • TerminalExpression(终结符表达式,如LiteralExpression)
    ——实现与文法中的终结符相关联的解释操作;
    ——一个句子的每个终结符需要该类的一个实例。
  • NonterminalExpression (非终结符表达式,如AlternationExpression,RepetitionExpression,SequenceExpressions)
    ——对文法中的每一条规则 R ::= R1R2. . . Rn都需要一个NonterminalExpression类;
    ——为从R1到Rn的每个符号都维护一个AbstractExpression类型的实例变量;
    ——为文法中的非终结符实现解释 ( Interpret )操作。解释( Interpret )一般要递归地调用表示R1到Rn的那些对象的解释操作。
6 协作
  • Client构建(或被给定)一个句子,它是NonterminalExpression和TerminalExpression的实例装配而成。
6 协作
  • 1 Client构建(或被给定)一个句子,它是由NonterminalExpression和TerminalExpression的实例装配而成的一个抽象语法树,然后初始化上下文并调用解释操作。
  • 2 每一非终结符表达式节点定义相应表达式的解释操作,而各终极符表达式的解释操作构成了递归的基础。
  • 3 每一节点的解释操作通过上下文来存储和访问解释器的状态。
7 效果

解释器模式有下列的优点和不足:

  • 1 易于改变和扩展文法 因为该模式使用类来表示文法规则 , 你可使用继承来改变或扩展该文法。已有的表达式可被增量式地改变 ,而新的表达式可定义为旧表达式的变体。
  • 2 也易于实现文法 定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可以用一个编译器或语法分析程序生成器自动生成。
  • 3 复杂的文法难以维护 解释器模式为文法中的每一条规则至少定义了一个类,因此包含许多规则的文法可能难以管理和维护。可应用其它的设计模式来缓解这一问题。但当文法非常复杂时,其它技术如语法分析程序或编译生成器更为合适。
  • 4 增加了新的解释表达式的方式 解释器模式使得实现新表达式“计算”变得容易。 例如,你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建新的解释表达式的方式, 那么可以考虑使用Visitor(5.11)模式以避免修改这些代表文法的类。
8 实现

Interpreter和Composite(4 . 3)模式在实现上有许多相通的地方。下面是Interpreter所要考虑的一些特殊问题:

  • 1 创建抽象语法树解释器模式并未解释如何创建一个抽象的语法树。换言之 , 它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来生成,也可用手写的 (通常为递归下降法) 语法分析程序创建,或直接由Client提供;
  • 2 定义解释操作 并不一定要在表达式类中定义解释操作。如果经常要创建一种新的解释器, 那么使用Visitor(5 . 11)模式将解释放入一个独立的 “访问者” 对象更好一些。例如,一个程序设计语言的会有许多在抽象语法树上的操作,比如类型检查、优化、代码生成,等等。恰当的做法是使用一个访问者以避免在每一个类上都定义这些操作。
  • 3 与Flyweight模式共享终结符 在一些文法中, 一个句子可能多次出现同一个终结符。此时最好共享那个符号的单个拷贝。计算机程序的文法是很好的例子 — 每个程序变量在整个代码中将会出现多次。在动机一节的例子中 , 一个句子中终结符dog (由LiteralExpression类描述)也可出现多次。终结节点通常不存储关于它们在抽象语法树中位置的信息。在解释过程中,任何它们所
    需要的上下文信息都由父节点传递给它们。因此在共享的 (内部的)状态和传入的(外部的)状态区分得很明确, 这就用到了Flyweight(4 . 6)模式。
9 代码示例

github地址

推荐阅读更多精彩内容