一 定义
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
文法:即语法规则。在解释器模式中每一个语法都将对应一个解释器对象,用来处理相应的语法规则。它对于扩展、改变文法以及增加新的文法规则都很方便。
解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
在解释器模式中可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例
二 UML图
- AbstractExpression:定义解释器的接口,约定解释器的解释操作。其中的Interpret接口,正如其名字那样,它是专门用来解释该解释器所要实现的功能。(如加法解释器中的Interpret接口就是完成两个操作数的相加功能)。
- TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。
- NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他解释器,如果用组合模式构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。
- Context:上下文,通常包含各个解释器需要的数据或是公共的功能。这个Context在解释器模式中起着非常重要的作用。一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
- Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树,然后调用解释操作。
三 例子
简单的计算器
#include <string>
//相当于AbstractExpression
class Expression
{
public:
virtual int interpret() = 0;
virtual ~Expression() {}
};
//数值 相当于 TerminalExpression
class VarExpression :public Expression
{
public:
VarExpression(int data) :m_nData(data) {}
int interpret()
{
return m_nData;
}
private:
int m_nData;
};
//相当于 NonterminalExpression
class SymbolExpression :public Expression
{
public:
SymbolExpression(Expression* left, Expression * right) :m_left(left), m_right(right) {}
~SymbolExpression()
{
if (m_left)
delete m_left;
if (m_right)
delete m_right;
}
protected:
Expression * m_left = NULL;
Expression * m_right = NULL;
};
class PlusExpression :public SymbolExpression
{
public:
PlusExpression(Expression* left, Expression * right) :SymbolExpression(left, right) {}
int interpret()
{
if(m_left && m_right)
return m_left->interpret() + m_right->interpret();
return 0;
}
};
class SubExpression :public SymbolExpression
{
public:
SubExpression(Expression* left, Expression * right) :SymbolExpression(left, right) {}
int interpret()
{
if (m_left && m_right)
return m_left->interpret() - m_right->interpret();
return 0;
}
};
//相当于 Context
class Calculator
{
public:
Calculator(string data):m_data(data){}
int getRes()
{
if (m_data.size() == 0)
return 0;
stack<Expression*> resStack;//栈 保存计算步骤
Expression * left = NULL;
Expression * right = NULL;
for (int i = 0; i < m_data.size(); i++)
{
char cdata = m_data[i];
switch (cdata)
{
case'+':
{
left = resStack.top();
resStack.pop();
right = new VarExpression(m_data[++i] - '0');
resStack.push(new PlusExpression(left, right));
break;
}
case'-':
{
left = resStack.top();
resStack.pop();
right = new VarExpression(m_data[++i] - '0');
resStack.push(new SubExpression(left, right));
break;
}
default:
resStack.push(new VarExpression(cdata - '0'));
}
}
left = resStack.top();
int res = (left == NULL) ? 0 : left->interpret();
delete left;//删除栈
return res;
}
private:
string m_data;
};
int main()
{
Calculator cal("1+2-3");
cout << "res=:" << cal.getRes() << endl;
getchar();
return 0;
}
只能做一位数的加减法,但是还是能看出这个模式。
四 解释器模式的优缺点
优点
- 易于实现文法:在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了,其他的都不用管。
- 易于扩展新的语法。由于解释器采用类来描述语法规则,因此可以通过继承等机制创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。
缺点
- 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
- 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
四 使用场合
在以下情况下可以考虑使用解释器模式:
1.可以将一个需要解释执行的语言中的句子表示为一个抽象语法树;
2.一些重复出现的问题可以用一种简单的语言来进行表达;
3.一个语言的文法较为简单;
4.执行效率不是关键问题。【注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。】
五 总结
目前个人来看,本质就是解析字符串