行为模式——解析器模式
1. 意图
给定一个语言,定义它的文法的一种表示,并定义一个解析器,这个解析器使用该表示来解释语言中的句子。
2. 动机
如果一种特定类型的问题的发生的频率足够高,那么可能就值得将该问题的各个实例表述为一个简单语句中的句子。这样就可以定义一个解析器,该解析器通过解释这些句子来解决该问题。
举个实际的例子,带有参数的表达式求值。之后的例子就是语言的布尔类型求值。首先,需要定义一个表达式,比如(true and x) or (y and (not x)),随着x,y的值的改变,我们需要计算不同的值,当如,解释器不是编译器,像这种语法的解析,解析器依然不是首选的设计模式,毕竟我们需要构建语法书,定义语法,然后再简化语法,再根据语法编译出这类东西的编译器,形式比这个要复杂的多,因此,这东西就显得有点鸡肋。不过,设计模式本来就是学习思想而不是学习这种模式的具体作用,存在即是合理。
3. 适用性
当有一个语言需要解析执行,并且你可将该语言的句子表示为一个抽象语法树时,可使用解析器模式。而存在以下情况时该模式效果最好:
- 该语法简单。对于复杂的语法树,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具时更高的选择。
- 效率不是一个关键问题。最高效的解析器通常不是直接解析语法分析书实现的,而是转化成另一种形式。
- 代码冗余很大。这可能是由于继承本身所带来的缺陷,一个特定的语法的都需要有一个类来支撑,当语法功能很多大的时候,解析器的代码极其多,而且绝大部分都存在重复。
4. 结构图
5. 参与者
- Context(上下文)
—— 包含解析器之外的一些上下文或者全局信息。 - AbstractExpression
—— 声明一个抽象的解析操作,这个接口为抽象语法树中所有的节点所共享。 - TerminalExpression
—— 实现与文法中的终结符相关联的解析器操作。
—— 一个句子中的每个终结符需要该类的一个实例。 - NormalminalExpression
—— 对文法的每一条规则R::=R1R2R3R4…都需要有一个NorterminalExpression。
—— 为文法中的非终结符实现解析操作。解释一般要递归地调用表示R1到Rn的那些对象的解析操作。
6. 协作
- Client构建一个句子它是AbstractExpression的一个实例,然后初始化上下文并调用解析操作。
- 每一个非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解析操作构成了递归的前提。
- 每一个节点的解析操作作用上下文来存储和访问解释器的状态。
7. 效果
- 易于改变和扩展文法。
- 易于实现。
- 复杂的文法难以维护。
- 增加了新的解释表达式的方法。
8. 实现
- 构建抽象语法树
- 定义解释操作。
- 与Flyweight模式共享终结符。
9. 代码示例
相关代码为解析Cpp的布尔类型的运算,相关代码在Github中:
https://github.com/VioletDream-SXZ/DesignPatterns/tree/master/BehaviorPattern
该项目的UML图如下:
参看文献
Erich Gamma,Richard Helm,Ralph Johnson,John Vissides.Design Patterns Elements of Reusable Object-Oriented Software[M].北京:机械工业出版社.2009:9.