解释器模式简介
解释器模式的定义为
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
①文法:即语法规则。在解释器模式中每一个语法都将对应一个解释器对象,用来处理相应的语法规则。它对于扩展、改变文法以及增加新的文法规则都很方便。
②解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
③在解释器模式中可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例
也就是说,这个模式更加适用于将数据加载为逻辑使用,换句话说,就是可以使得数据与逻辑实现了一定的分离,也能够使得一些没有编程基础的人也能够将正常的语法变为机器能够实别的语言
比如输入:1+2+3-4-5
计算机能够正确的识别语句
当然,与一些简单的逻辑堆积的写法不同,解释器模式就是对这些的逻辑进行了解耦操作,使得扩展性得到了提高
结构
解释器模式的结构分为三部分
第一部分是Expression(解释器的接口)
①Expression:定义解释器的接口,约定解释器的解释操作。其中的Interpret接口,正如其名字那样,它是专门用来解释该解释器所要实现的功能。(如加法解释器中的Interpret接口就是完成两个操作数的相加功能)。
②TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。
③NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他解释器,如果用组合模式构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。
第二部分则是Context(环境)
Context:上下文,通常包含各个解释器需要的数据或是公共的功能。这个Context在解释器模式中起着非常重要的作用。一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
第三部分则是Client(客户)
Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树,然后调用解释操作。
使用解释器模式来简单实现加减法运算
其中VariableExpression就是终结解释符,意为数字将为算式的结束的位置,而OperationExpression的各种派生的子类则是算式的运算符了,在这里就是用于将左边和右边的操作符进行运算
首先先看看基类Expression
class Expression
{
public:
Expression();
virtual ~Expression();
virtual double evaluate() = 0;
};
在这里,首先就是定义了解释器所需要的解释的东西,也就是计算了
然后就开始定义具体的解释内容了,首先是终结符解释器
//.h
class NumberExpression :
public Expression
{
private:
double value_;
public:
NumberExpression(double value);
~NumberExpression();
double evaluate() override;
};
//.cpp
NumberExpression::NumberExpression(double value)
:value_(value)
{
}
NumberExpression::~NumberExpression()
= default;
double NumberExpression::evaluate() {
return value_;
}
在数字的解释器,evaluate()
出来的内容也只能是自己的值了
然后,则是非终结符解释器OperationExpression
//.h
class OperationExpression :
public Expression
{
public:
OperationExpression(Expression * left,Expression * right);
~OperationExpression();
protected:
Expression * left_;
Expression * right_;
};
//.cpp
OperationExpression::OperationExpression(Expression* left, Expression* right)
:left_(left),
right_(right)
{
}
OperationExpression::~OperationExpression()
= default;
操作符运算符需要的就是将左边的与右边的计算在一起,因此就需要左边与右边的两个计算符。
之后则是具体实现SubExpression
//.h
class SubExpression :
public OperationExpression
{
public:
SubExpression(Expression * left,Expression *right);
~SubExpression();
double evaluate() override;
};
//.cpp
SubExpression::SubExpression(Expression* left, Expression* right)
:OperationExpression(left,right)
{
}
SubExpression::~SubExpression()
= default;
double SubExpression::evaluate()
{
return left_->evaluate() - right_->evaluate();
}
AddExpression
//.h
class AddExpression :
public OperationExpression
{
public:
AddExpression(Expression * left,Expression * right);
~AddExpression();
double evaluate() override;
};
//.cpp
AddExpression::AddExpression(Expression* left, Expression* right)
: OperationExpression(left,right)
{
}
AddExpression::~AddExpression()
= default;
double AddExpression::evaluate()
{
double left = left_->evaluate();
double right = right_->evaluate();
return left + right;
}
之后则是Context则是记录一些解释时字符串与数字的变换法则
//.h
class Context
{
public:
Context();
~Context();
int getVariable(char key);
private:
std::map<char, int> variable;
};
//.cpp
Context::Context()
{
variable.insert(std::pair<char, int>('0', 0));
variable.insert(std::pair<char, int>('1', 1));
variable.insert(std::pair<char, int>('2', 2));
variable.insert(std::pair<char, int>('3', 3));
variable.insert(std::pair<char, int>('4', 4));
variable.insert(std::pair<char, int>('5', 5));
variable.insert(std::pair<char, int>('6', 6));
variable.insert(std::pair<char, int>('7', 7));
variable.insert(std::pair<char, int>('8', 8));
variable.insert(std::pair<char, int>('9', 9));
}
Context::~Context()
= default;
int Context::getVariable(char key)
{
if(variable[key] != NULL)
{
return variable[key];
}
return VALUE_NOT_EXIST;
}
以及Client,也是解释器的客户端,解释器的逻辑的实现类,里面包含了所有的解释语言以及解释语言的环境(Context与Expression)
//.h
class Calculator
{
public:
Calculator(std::string str);
~Calculator();
void calculate() const;
private:
std::string str_;
Context * context_;
};
//.cpp
Calculator::Calculator(const std::string str)
{
this->str_ = str;
this->context_ = new Context();
}
Calculator::~Calculator()
{
delete context_;
}
void Calculator::calculate() const
{
const int len = str_.length();
if (len == 0)
{
return;
}
Expression* left = new NumberExpression(context_->getVariable(str_[0]));
Expression* right = nullptr;
auto* stack = new std::stack<Expression *>();
stack->push(left);
for (auto i = 1; i < len; i += 2)
{
left = stack->top();
stack->pop();
right = new NumberExpression(context_->getVariable(str_[i + 1]));
switch (str_[i])
{
case '+':
stack->push(new AddExpression(left, right));
break;
case '-':
stack->push(new SubExpression(left, right));
break;
case '*':
stack->push(new MulExpression(left, right));
break;
case '/':
stack->push(new DivExpression(left, right));
break;
default:
std::cout << "input error" << std::endl;
break;;
}
}
auto answer = stack->top();
std::cout << "answer : " << answer->evaluate() << std::endl;
delete stack;
}
最后则是在main()
当中的实现
using namespace std;
int main(int argc, char* argv[])
{
string str;
cout << "请输入算术表达式" << endl;
cin >> str;
Calculator * calculator = new Calculator(str);
calculator->calculate();
cin >> str;
return 0;
}
很简单也很方便的实现了逻辑的计算,若要以后为这个程序加上乘和除也很简单,只要继续添加Expression,并修改Client当中的逻辑树就可以了,这样便可以达到了一个易于扩展的目的
结语
在我看来,解释器模式其实更加倾向于一种将数据与逻辑分离,比如将数据写入到.txt
当中,然后,程序读入.txt
的内容并将相对于的内容写入到内存当中,从而达到了一种简单修改某种物体的参数(比如修改游戏中某种怪物的血量,这样就可以只修改配置文件而不用去修改代码)或者简单的使非编程人员建立逻辑(比如用户在游戏编辑器当中的自定义怪物)的效果