1. 解释器模式(Interpreter Pattern)的定义
(1)定义
给定一个语言,定义它的文法的一种表示,并定义一个解释器,这个解释器使用该表示来解释语言中的句子。
①文法:即语法规则。在解释器模式中每一个语法都将对应一个解释器对象,用来处理相应的语法规则。它对于扩展、改变文法以及增加新的文法规则都很方便。
②解释器模式描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
③在解释器模式中可以通过一种称之为抽象语法树(Abstract Syntax Tree, AST)的图形方式来直观地表示语言的构成,每一棵抽象语法树对应一个语言实例
(2)解释器模式的结构和说明
①AbstractExpression:定义解释器的接口,约定解释器的解释操作。其中的Interpret接口,正如其名字那样,它是专门用来解释该解释器所要实现的功能。(如加法解释器中的Interpret接口就是完成两个操作数的相加功能)。
②TerminalExpression:终结符解释器,用来实现语法规则中和终结符相关的操作,不再包含其他的解释器,如果用组合模式来构建抽象语法树的话,就相当于组合模式中的叶子对象,可以有多种终结符解释器。
③NonterminalExpression:非终结符解释器,用来实现语法规则中非终结符相关的操作,通常一个解释器对应一个语法规则,可以包含其他解释器,如果用组合模式构建抽象语法树的话,就相当于组合模式中的组合对象。可以有多种非终结符解释器。
④Context:上下文,通常包含各个解释器需要的数据或是公共的功能。这个Context在解释器模式中起着非常重要的作用。一般用来传递被所有解释器共享的数据,后面的解释器可以从这里获取这些值。
⑤Client:客户端,指的是使用解释器的客户端,通常在这里将按照语言的语法做的表达式转换成使用解释器对象描述的抽象语法树,然后调用解释操作。
【编程实验】四则运算(注意终结符解释器与非终结解释器的划分)
//行为型模式:解释器模式 //场景:四则运算 #include <iostream> #include <string> #include <map> #include <stack> #include <typeinfo> using namespace std; //*******************************************抽象表达式类*********************************** class Expression { public: //解析公式和数值,其中var中的key是公式中的参数,value值是具体的数字 //如a = 100; b = 20; c = 40 virtual int interpreter(map<string, int>& var) = 0; virtual ~Expression(){}; }; //变量解析器(终结符表达式) class VarExpression : public Expression { string key; public: VarExpression(string key) { this->key = key; } //从map中取出变量的值 int interpreter(map<string, int>& var) { return var[key]; } ~VarExpression() { cout << "~VarExpression()" << endl; } }; //**********抽象运算符号解析器*********************** //抽象运算符号解析器 class SymbolExpression : public Expression { protected: Expression* left; Expression* right; public: SymbolExpression(Expression* left, Expression* right) { this -> left = left; this -> right = right; } Expression* getLeft() { return left; } Expression* getRight() { return right; } }; //加法解析器 class AddExpression : public SymbolExpression { public: AddExpression(Expression* left, Expression* right): SymbolExpression(left,right) { } //把左右两个表达式运算的结果加起来 int interpreter(map<string, int>& var) { return left->interpreter(var) + right ->interpreter(var); } ~AddExpression() { cout << "~AddExpression()" << endl; } }; //减法解析器 class SubExpression : public SymbolExpression { public: SubExpression(Expression* left, Expression* right): SymbolExpression(left,right) { } //把左右两个表达式运算的结果相减 int interpreter(map<string, int>& var) { return left->interpreter(var) - right ->interpreter(var); } ~SubExpression() { cout << "~SubExpression()" << endl; } }; //*********************************解析器封装类*************************************** //解析器封装类,这个类是根据迪米特法则进行封装,目的是让Client只与直接朋友打交道,相当于Facade class Calculator { private: Expression* expression; public: //构造函数传参,并解析表达式,构建语法树 Calculator(string expStr) { expression = NULL; //栈,用来暂存中间结果 stack<Expression*> stkExp; Expression* left = NULL; Expression* right = NULL; /*从左到向分析表达式(如:a+b-c),最终的语法树如下: * - * / \ * + c * / \ * a b */ for(unsigned int i = 0; i< expStr.length(); i++) { switch(expStr[i]) { case '+': //加法 //1.先从栈中取出左操作数 left = stkExp.top(); stkExp.pop(); //2.从表达式中取出+号后面的右操作数,并生成终结符解析对象 right = new VarExpression(expStr.substr(++i,1)); //3.将左右操作数相加,并把结果放入栈中 stkExp.push(new AddExpression(left, right)); break; case '-': //1.先从栈中取出左操作数 left = stkExp.top(); stkExp.pop(); //2.从表达式中取出+号后面的右操作数,并生成终结符解析对象 right = new VarExpression(expStr.substr(++i,1)); //3.将左右操作数相减,并把结果放入栈中 stkExp.push(new SubExpression(left, right)); break; default: //如果是变量(终结符):如a+b+c中的a\b\c, //则直接生成对应的变量解析器对象 stkExp.push(new VarExpression(expStr.substr(i,1))); } } //栈中保存的就是最终语法树的根结点(本例为SuuExpression对象) if(!stkExp.empty()) { expression = stkExp.top(); stkExp.pop(); } } void deltree(Expression* expression) { SymbolExpression* branch = dynamic_cast<SymbolExpression*>(expression); //叶子结点 if (branch == NULL) { delete expression; } else //分支结点 { //左子树 deltree(branch->getLeft()); //右子树 deltree(branch->getRight()); //结点 delete expression; } } ~Calculator() { deltree(expression); expression = NULL; } //开始运算 int run(map<string, int>& var) { return (expression == NULL) ? 0 : expression->interpreter(var); } }; int main() { string expStr = "a+b-c"; //为简化处理,这里必须是合法的表达式 map<string, int> var; //相当于Interpreter模式中的Context var["a"] = 100; var["b"] = 20; var["c"] = 40; Calculator cal(expStr); cout <<"运算结果为:" << expStr << " = " << cal.run(var) << endl; return 0; } /* 运算结果为:a+b-c = 80 ~VarExpression() ~VarExpression() ~AddExpression() ~VarExpression() ~SubExpression() */
2. 思考解释器模式
(1)解释器模式的本质:分离实现,解释执行。通过一个解释器对象处理一个语法规则的方式,把复杂的功能分离开,然后选择需要被执行的功能,并把这些功能组合成需要解释执行的抽象语法树,再按照抽象语法树来解释执行,实现相应的功能。从本质上看,解释器模式的思路仍然是分离、封装和简化,这与很多其他模式是一样的。
(2)谁来构建抽象语法树——解析器
解析器的工作主要就是用来构建抽象语法树的,这个角色会客户端转来的语言,负责按语法规则,生成一个个解释器,并将这些解释器组合成一颗抽象语法树。注意,解析器的工作主要是构造语法树,而解释器的工作是解释这颗语法树。
(3)谁来负责解释操作 ——解释器(Interpreter)
只要定义好了抽象语法树,肯定是解释器来负责解释执行,而选择解释器的工作,在构建抽象语法树的时候就完成了,一般由根结点的解释器开始解释,然后递归地调用其他解释器。
【编程实验】中文数字转阿拉伯数字(注意Context的应用)
//行为型模式:解释器模式 //场景:中文数字转阿拉伯数字 /* 1、使用Interpreter模式来将中文数字转换为数学数字的好处是可以应对中文数字的变化, 虽然可以用一很好的算法将中文转化为数字,解释器的扩展性能比较好,如果出现亿、兆的情况, 可以写出两个类(YiExpression、ZhaoExpression)来继承Expression类,而其他地方的代码都不变化。 2、思路:用单位用分解出不同的解释器.其中个位、十位、百位和千位是终结符解释器,万位是非结终符 解释器,因为万以上的单位可以形成如果九百零一万之类的数字,需要进一进拆分成由结终符 构成的解释器来完成任务。 3、转化:将中文数字串由低位向高位方向不断转化 */ #include <iostream> #include <string> #include <map> #include <stack> #include <list> using namespace std; //字符串上下文信息:保存没有处理的字符串信息 class Context { private: string statement; int data; public: Context(string statement) { this ->statement = statement; data = 0; } string& getStatement() { return statement; } void setStatement(string statement) { this -> statement = statement; } int getData() { return data; } void setData(int data) { this -> data = data; } }; //抽象类:解释器 class Expression { protected: //数据字典:保存中文数字一到九 static map<string, int> table; //辅助函数,用来判判断src字符串是否以tail串结尾 bool stringEndsWith(const string& src, const string& tail) { if(src.size() < tail.size()) return false; string tmp = src.substr(src.size() - tail.size(), tail.size()); return (0==tmp.compare(0,tail.size(),tail)); } public: //虚方法:中文数字到数字的转换 virtual void interpret(Context& context) { if(context.getStatement().length() == 0) return; map<string, int>::iterator iter = table.begin(); while (iter != table.end()) { string& statement = context.getStatement(); string tail = iter->first + getPostfix(); //从低位往高位分析(如九千三百零五,从右向左分析) if(stringEndsWith(statement,tail)) { context.setData(context.getData() + iter->second * multiplier()); //注意,string是ASCII编码,每个中文字符的长度为2 context.setStatement(statement.substr(0, statement.length()-2 - getPostfix().length())); } if(stringEndsWith(statement,"零")) { //”零“则直接跳过 context.setStatement(statement.substr(0, statement.length()-2)); } ++iter; } } //表达式的后缀是以什么表示的(十、百...) virtual string getPostfix() = 0; //表达式的数量级 virtual int multiplier() = 0; virtual ~Expression(){}; }; //映射表,保存中文数字与罗马数字的映射 static map<string, int>::value_type init_table[] = { map<string, int>::value_type("一",1), map<string, int>::value_type("二",2), map<string, int>::value_type("三",3), map<string, int>::value_type("四",4), map<string, int>::value_type("五",5), map<string, int>::value_type("六",6), map<string, int>::value_type("七",7), map<string, int>::value_type("八",8), map<string, int>::value_type("九",9) }; map<string,int> Expression::table(init_table,init_table + 9); //个位数解释器(终结符表达式) class GeExpression : public Expression { public: string getPostfix() { return ""; } int multiplier() { return 1; } }; //十位数解释器(终结符表达式) class ShiExpression : public Expression { public: string getPostfix() { return "十"; } int multiplier() { return 10; } }; //百位数解释器(终结符表达式) class BaiExpression : public Expression { public: string getPostfix() { return "百"; } int multiplier() { return 100; } }; //千位数解释器(终结符表达式) class QianExpression : public Expression { public: string getPostfix() { return "千"; } int multiplier() { return 1000; } }; //万位数解释器(非终结符表达式) class WanExpression : public Expression { public: string getPostfix() { return "万"; } int multiplier() { return 10000; } void interpret(Context& context) { if(context.getStatement().length() == 0) return ; if (stringEndsWith(context.getStatement(),getPostfix())) { list<Expression*> exps; exps.clear(); exps.push_back(new GeExpression()); exps.push_back(new ShiExpression()); exps.push_back(new BaiExpression()); exps.push_back(new QianExpression()); int temp = context.getData(); string& sm = context.getStatement(); context.setData(0); //string类中每个中文长度为2. context.setStatement(sm.substr(0, sm.length()-2)); list<Expression*>::iterator iter = exps.begin(); while (iter != exps.end()) { (*iter)->interpret(context); ++iter; } context.setData(temp + multiplier()* context.getData()); iter = exps.begin(); while (iter != exps.end()) { delete (*iter); ++iter; } exps.clear(); } } }; //转换器 class Convertor { private: Context context; string chineseNum; int result; list<Expression*> exps; void reset() { context.setStatement(chineseNum); context.setData(0); list<Expression*>::iterator iter = exps.begin(); while (iter != exps.end()) { delete (*iter); ++iter; } exps.clear(); } public: Convertor(const string chineseNum):context(chineseNum) { this ->chineseNum = chineseNum; result = 0; } void convert() { reset(); exps.push_back(new GeExpression()); exps.push_back(new ShiExpression()); exps.push_back(new BaiExpression()); exps.push_back(new QianExpression()); exps.push_back(new WanExpression()); list<Expression*>::iterator iter = exps.begin(); while (iter != exps.end()) { (*iter)->interpret(context); ++iter; } result = context.getData(); } int getRoman() { return result; } void setChineseNum(const string& chineseNum) { this ->chineseNum = chineseNum; } ~Convertor() { reset(); } }; int main() { string chineseNum = "四百九十六万二千三百一十五"; Convertor conv(chineseNum); conv.convert(); cout << chineseNum << " -> " << conv.getRoman() << endl; chineseNum = "九千零五万六千零七十二"; conv.setChineseNum(chineseNum); conv.convert(); cout << chineseNum << " -> " << conv.getRoman() << endl; return 0; } /*输出结果: 四百九十六万二千三百一十五 -> 4962315 九千零五万六千零七十二 -> 90056072 */
3. 解释器模式的优缺点
(1)优点
①易于实现文法:在解释器模式中,一条语法规则用一个解释器对象来解释执行。对于解释器的实现来讲,功能就变得比较简单,只需要考虑这一条语法规则的实现就可以了,其他的都不用管。
②易于扩展新的语法。由于解释器采用类来描述语法规则,因此可以通过继承等机制创建相应的解释器对象,在创建抽象语法树的时候使用这个新的解释器对象就可以了。
(2)缺点
①执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
②对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
4. 解释器模式的应用场景
(1)当一个语言需要解释执行,并可以将该语言中的句子表示为一个抽象语法树的时候,可以考虑使用解释器模式(如XML文档解释、正则表达式等领域)
(2)一些重复出现的问题可以用一种简单的语言来进行表达。
(3)一个语言的文法较为简单.
(4)当执行效率不是关键和主要关心的问题时可考虑解释器模式(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)
【编程实验】机器人控制程序
//行为型模式:解释器模式 //场景:开发一套机器人控制程序 /*说明: 机器人控制程序中包含一些简单的英文控制指令,每一个指令对应一个表达式(expression), 该表达式可以是简单表达式也可以是复合表达式,每一个简单表达式由移动方向(direction), 移动方式(action)和移动距离(distance)三部分组成,其中移动方向包括上(up)、下(down)、 左(left)、右(right);移动方式包括移动(move)和快速移动(run);移动距离为一个正整数。 两个表达式之间可以通过与(and)连接,形成复合(composite)表达式。 用户通过对图形化的设置界面进行操作可以创建一个机器人控制指令,机器人在收到指令 后将按照指令的设置进行移动,例如输入控制指令:up move 5,则“向上移动5个单位”;输入控 制指令:down run 10 and left move 20,则“向下快速移动10个单位再向左移动20个单位”。 */ /*文法规则 expression ::= direction action distance | composite //表达式 composite ::= expression 'and' expression //复合表达式 direction ::= 'up' | 'down' | 'left' | 'right' //移动方向 action ::= 'move' | 'run' //移动方式 distance ::= an integer //移动距离 上述语言一共定义了五条文法规则,对应五个语言单位,这些语言单位可以分为两类, 终结符(也称为终结符表达式):例如direction、action和distance,它们是语言的最小组成单位,不能再进行拆分; 非终结符(也称为非终结符表达式),例如expression和composite,它们都是一个完整的句子,包含一系列终结符或非终结符。 */ //本实例对机器人控制指令的输出结果进行模拟,将英文指令翻译为中文指令,实际情况是调用不同的控制程序进行机器人的控制, //包括对移动方向、方式和距离的控制等 #include <iostream> #include <string> #include <stack> #include <vector> #include <typeinfo> using namespace std; ////////////////////////////////////////////////////////////////////////// // 字符串分割 // // ------------------------------------------------------------------------- // 函数 : Split // 功能 : 分割STL标准字符串 // 返回值 : void // 参数 : Container<std::basic_string<CharT> >& v 存放分割结果 // 参数 : const std::basic_string<CharT>& s 待分割字符串 // 参数 : const std::basic_string<CharT>& c 分割字符串 // ------------------------------------------------------------------------- template<typename CharT, template<typename S, typename Q = std::allocator<S> > class Container> void Split(Container<std::basic_string<CharT> >& v, const std::basic_string<CharT>& s, const std::basic_string<CharT>& c); template<template<typename S, typename Q = std::allocator<S> > class Container> void Split(Container<std::basic_string<char> >& v, const std::basic_string<char>& s, const std::basic_string<char>& c) { if (0 == c.length()) return; std::basic_string<char>::size_type pos1 = 0; std::basic_string<char>::size_type pos2 = 0; pos1 = 0; pos2 = s.find(c); while (std::basic_string<char>::npos != pos2) { v.push_back(s.substr(pos1, pos2 - pos1)); pos1 = pos2 + c.size(); pos2 = s.find(c, pos1); } if (pos1 != s.length()) { v.push_back(s.substr(pos1)); } } //抽象类:解释器 class AbstractExpression { public: virtual string interpret() = 0; virtual ~AbstractExpression(){} }; //And解释器:非终结符表达式 class AndExpression : public AbstractExpression { private: AbstractExpression* left ; //And的左表达式 AbstractExpression* right; //And的右表达式 public: AndExpression(AbstractExpression* left, AbstractExpression* right) { this->left = left; this->right = right; } //And表达式解释操作 string interpret() { return left->interpret() + "再" + right->interpret(); } AbstractExpression* getLeft() { return left; } AbstractExpression* getRight() { return right; } }; //简单句子解释器:非终结符表达式 //如:up move 5,表示“向上移动5个单位 class SentenceExpression : public AbstractExpression { private: AbstractExpression* direction; AbstractExpression* action; AbstractExpression* distance; public: SentenceExpression(AbstractExpression* direction, AbstractExpression* action, AbstractExpression* distance) { this->direction = direction; this->action = action; this->distance = distance; } //解释操作 string interpret() { return direction->interpret() + action->interpret() + distance->interpret(); } AbstractExpression* getDirection() { return direction; } AbstractExpression* getAction() { return action; } AbstractExpression* getDistance() { return distance; } }; //方向解释器:终结符表达式 class DirectionExpression : public AbstractExpression { private: string direction; public: DirectionExpression(string direction) { this->direction = direction; } //方向表达式的解释操作 string interpret() { if(direction =="up") { return "向上"; } else if(direction == "down") { return "向下"; } else if(direction == "left") { return "向左"; } else if(direction == "right") { return "向右"; } else { return "无效指令"; } } }; //动作解释器:(终结符表达式) class ActionExpression : public AbstractExpression { private: string action; public: ActionExpression(string action) { this->action = action; } //动作移动表达式的解释操作 string interpret() { if(action == "move") { return "移动"; } else if(action == "run") { return "快速移动"; } else { return "无效指令"; } } }; //距离解释器:(终结符表达式) class DistanceExpression : public AbstractExpression { private: string distance; public: DistanceExpression(string distance) { this->distance = distance; } string interpret() { return distance; } }; //指令处理类:工具类 class InstructionHandler { private: AbstractExpression* mExp; void delTree(AbstractExpression* exp) //删除最终的生成的抽象树 { //叶子结果 bool bLeaf = typeid(*exp) ==typeid(DirectionExpression) || typeid(*exp) ==typeid(ActionExpression) || typeid(*exp) ==typeid(DistanceExpression); AndExpression* andExp = dynamic_cast<AndExpression*>(exp); SentenceExpression* sentenceExp = dynamic_cast<SentenceExpression*>(exp); if(bLeaf) { delete exp; } else if (andExp != NULL) //And表达式 { AbstractExpression* left = andExp->getLeft(); AbstractExpression* right = andExp->getRight(); delTree(left); delTree(right); delete andExp; } else if(sentenceExp != NULL) //简单句子表达式 { AbstractExpression* dir = sentenceExp->getDirection(); AbstractExpression* act = sentenceExp->getAction(); AbstractExpression* dis = sentenceExp->getDistance(); delTree(dir); delTree(act); delTree(dis); delete sentenceExp; } else { } } public: InstructionHandler():mExp(NULL){} void handle(string instruction){ AbstractExpression* left = NULL; AbstractExpression* right = NULL; AbstractExpression* direction = NULL; AbstractExpression* action = NULL; AbstractExpression* distance = NULL; if (mExp!=NULL) { delTree(mExp); mExp = NULL; } //声明一个栈对象用于存储抽象语法树 stack<AbstractExpression*> stkExp; vector<string> words; Split(words, instruction, " "); //以空格分隔指令字符串 for(unsigned int i=0; i<words.size(); i++){ //本实例采用栈的方式来处理指令,如果遇到and则将其后的三个单词连成一个简单句子(Sentence) //作为"and"的右表达式,而将栈顶弹出的表达式作为"and"的左表达式,最后将新的And表达式压入栈中 string dir(""); string act(""); string dis(""); if(words[i] =="and"){ //从弹出栈顶作为and的左表达式 left = stkExp.top(); stkExp.pop(); dir = words[++i]; direction = new DirectionExpression(dir); act = words[++i]; action = new ActionExpression(act); dis = words[++i]; distance = new DistanceExpression(dis); //组成一个简单表达式作为And的右表达式 right = new SentenceExpression(direction, action, distance); //生成And表达式,并压入栈中 stkExp.push(new AndExpression(left, right)); } //如果不是and表达式,就从头开始进行解释,将前3个单词作为Sentence //的三个操作数,生成简单表达式解析器后压入栈中 else{ dir = words[i]; direction = new DirectionExpression(dir); act = words[++i]; action = new ActionExpression(act); dis = words[++i]; distance = new DistanceExpression(dis); //组成一个简单表达式作为And的右表达式 stkExp.push(new SentenceExpression(direction, action, distance)); } } if(!stkExp.empty()){ mExp = stkExp.top(); stkExp.pop(); } else mExp = NULL; } string output(){ return mExp==NULL ? "": mExp->interpret(); } }; int main() { string instruction = "up move 5 and down run 10 and left move 5"; InstructionHandler handler; handler.handle(instruction); cout <<"输入指令: " <<instruction <<endl; cout <<"移动结果:" << handler.output() << endl; instruction = "right run 20 and down move 10 and left run 40 and up run 10"; handler.handle(instruction); cout <<"输入指令: " <<instruction <<endl; cout <<"移动结果:" << handler.output() << endl; return 0; } /*输出结果: 输入指令: up move 5 and down run 10 and left move 5 移动结果:向上移动5再向下快速移动10再向左移动5 输入指令: right run 20 and down move 10 and left run 40 and up run 10 移动结果:向右快速移动20再向下移动10再向左快速移动40再向上快速移动10 */
5. 相关模式
(1)解释器和组合模式
这两种可以组合使用,一般非终结符解释器相当于组合模式中的组合对象,终结符解释器相当于叶子对象。
(2)解释器模式和迭代器模式
由于解释器模式通常使用组合模式来实现,因此在遍历整个对象结构时,可以使用迭代器模式。
(3)解释器模式和享元模式
在使用解释器模式的时候,可能会造成多个细粒度对象,如各式各样的终结符解释器,而这些终结符解释器对不同的表达式来说是一样的,是可以共用的,因此可以引入享元模式来共享这些对象。
(4)解释器模式和访问者模式
在解释器模式中,语法规则和解释器对象是有对应关系的。语法规则的变动意味着功能的变化。自然会导致使用不同的解释器对象;而且一个语法规由可以被不同的解释器解释执行。因此在构建抽象语法树的时候,如果每个节点所对应的解释器对象是固定的,这意味着该节点对应的功能是固定的,那么就不得不根据需要来构建不同的抽象语法树。
为了让构建的抽象语法树较为通用,那就要求解释器的功能不要那么固定,要能很方便地改变解释器的功能,这个时候就变成了如何能够很方便地更改树形结构中节点对象的功能了,访问者模式可以很好的实现这个功能。