意图
以类来表达语法规则
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
适用性
当有一个语言需要解释执行,并且你可将该语言中的句子表示为一个抽象语法树时,可使用解释器模式。而当存在以下情况时该模式效果最好:
1. 该文法简单对于复杂的文法,文法的类层次变得庞大而无法管理。此时语法分析程序生成器这样的工具是更好的选择。它们无需构建抽象语法树即可解释表达式,这样可以节省空间而且还可能节省时间。
2. 效率不是一个关键问题最高效的解释器通常不是通过直接解释语法分析树实现的,而是首先将它们转换成另一种形式。例如,正则表达式通常被转换成状态机。但即使在这种情况下,转换器仍可用解释器模式实现,该模式仍是有用的。
3. 如果一种特定类型的问题发生的频率足够高
4. 文法简单,不考虑效率
5. 可以处理脚本语言和编程语言
结构
参与者
AbstractExpression(抽象表达式)
声明一个抽象的解释操作,这个接口为抽象语法树中的所有的节点所共享,这个接口为所有具体表达式角色(抽象语法树中的节点)都要实现的。
什么叫做抽象语法树呢?《java 与模式》中给的解释为:抽象语法树的每一个节点都
代表一个语句,而在每个节点上都可以执行解释方法。这个解释方法的执行就代表这个语句被解释。由于每一个语句都代表这个语句被解释。由于每一个语句都代表一个常见的问题的实例,因此每一个节点上的解释操作都代表对一个问题实例的解答。
TerminalExpression(终结符表达式)
- 实现与文法中的终结符相关联的解释操作
- 一个句子中的每个终结符需要该类的一个实例
NonterminalExpression(非终结符表达式)
- 对文法中的每一条规则 R ::= R1R2..Rn都需要一个NonterminalExpression类
- 对于从R1到Rn的每个符号都维护一个AbstractExpression的实例变量
- 为文法中的非终结符实现解释(Interpreter)操纵。解释(Interpreter)一般要递归地调用表示R1到Rn的那些对象的解释操作。
Context(上下文)
包含解释器之外的一些全局信息。
Client(客户)
- 构建(或被给定)表示该文法定义的语言中一个特定的句子的抽象语法树。该抽象语法树由NonterminalExpression和TerminalExpression的实例装配而成。
- 调用解释操作。
代码
AbstractExpression
public abstract class Expression {
public abstract int interpret(Context con);
}
TerminalExpression
public class TerminalExpression extends Expression{
private int i;
public int interpret(Context con) {
return i;
}
public TerminalExpression(int i){
this.i = i;
}
}
public class Variable extends Expression{
public int interpret(Context con) {
return con.getContextValue(this);
}
}
NonTerminalExpression
public class AddNonTermainalExpression extends Expression{
private Expression left,right;
public AddNonTermainalExpression(Expression left,Expression right){
this.left = left;
this.right = right;
}
public int interpret(Context con) {
return left.interpret(con)+right.interpret(con);
}
}
public class MutiplyNonTermainalExpression extends Expression{
private Expression left,right;
public MutiplyNonTermainalExpression(Expression left,Expression right){
this.left = left;
this.right = right;
}
public int interpret(Context con) {
return left.interpret(con)*right.interpret(con);
}
}
Context
public class Context {
private Map<Variable,Integer> contextMap = new HashMap<Variable,Integer>();
public void addContextValue(Variable x,Integer y){
contextMap.put(x, y);
}
public int getContextValue(Variable x){
return contextMap.get(x).intValue();
}
}
Client
public class Client {
private static Expression ex;
private static Context con;
public static void main(String[] args) {
con = new Context();
Variable a = new Variable();
Variable b = new Variable();
TerminalExpression c = new TerminalExpression(2);
con.addContextValue(a, 5);
con.addContextValue(b, 7);
ex = new AddNonTermainalExpression(new MutiplyNonTermainalExpression(a, b), new AddNonTermainalExpression(a, c));
System.out.println(ex.interpret(con));
}
}
协作
- Client构建(或被给定)一个句子,它是NonterminalExpression和TerminalExpression的实例的一个抽象语法树。然后初始化上下文并调用解释操作。
- 每一个非终结符表达式节点定义相应子表达式的解释操作。而各终结符表达式的解释操作构成了递归的基础。
- 每一节点的解释操作用上下文来存储和访问解释器的状态。
效果
易于改变和扩展文法
因为该模式使用类来表示文法规则,你可使用继承来改变或扩展该文法。已有的表达式可被增量式的改变,而新的表达式可定义为旧表达式的变体。
易于实现文法
定义抽象语法树中各个节点的类的实现大体类似。这些类易于直接编写,通常它们也可用一个解释器或语法分析程序生成器自动生成。
复杂的文法难以维护
解释器模式为文法中的每一条规则至少定义了一个类(使用BNF定义的文法规则需要更多的类)。因此包含许多规则的文法可能难以管理和维护。可应用其他的设计模式来缓解这一问题。但当文法非常复杂时,其他的技术如语法分析程序或编译器生成器更为合适。
增加了新的解释表达式的方式
解释器模式使得实现新表达式“计算”变得容易。例如,你可以在表达式类上定义一个新的操作以支持优美打印或表达式的类型检查。如果你经常创建新的解释表达式的方式,那么可以考虑使用Visitor模式以避免修改这些代表文法的类。
优点
解释器模式提供了一个简单的方式来执行语法,而且容易修改和扩展语法。
缺点
当语法规则数目太大时,这个模式可能会变得非常繁琐。
实现
创建抽象语法树
解释器模式并未解释如何创建一个抽象的语法树。换言之,它不涉及语法分析。抽象语法树可用一个表驱动的语法分析程序来生成,也可用手写的(通常为递归下降法)语法分析程序创建,或直接由Client提供。
定义解释操作
并不一定要在表达式类中定义解释操作。如果经常要创建一种新的解释器,那么使用Visitor模式将解释放入一个独立的“访问者”对象更好一些。例如,一个程序设计语言的会有许多在抽象语法分析树上的操作,比如类型检查、优化、代码生成,等等。恰当的做法是使用一个访问者以避免在每一个类上都定义这些操作。
与Flyweight模式共享终结符
在一些文法中,一个句子可能多次出现同一个终结符。此时最好共享那个符号的单个拷贝。计算机程序的文法是最好的例子,每个程序变量在整个代码中将会出现多次。
终结节点通常不存储关于它们在抽象语法树中位置的信息。在解释过程中,任何它们所需要的上下文信息都由父节点传递给它们。因此在共享的(内部的)状态和传入的(外部的)状态区分得很明确,这就用到了Flyweight模式。
经典例子
正则表达式、实现计算器
相关模式
Iterator
解释器可用一个迭代器遍历该结构。
Composite Pattern
NonterminalExpression参与者通常都会有递归结构,因此会用到组合模式。抽象语法树是一个复合模式的实例。
Flyweight Pattern
有时TerminalExpression参与者会使用到享元模式进行对象共享。说明了如何在抽象语法树中共享终结符。
Visitor Pattern
完成树状剖析之后,穿梭在树状剖析的各个节点间进行处理,可能会用到Visitor Pattern。可用来在一个类中维护抽象语法树中的各节点的行为。
敬请期待“迭代模式(Iterator Pattern 游标模式 CursorPattern,对象行为型模式)”