十七、解释器模式(Interpreter
Pattern)
概述:
未来机器智能化已然成为趋势,现在手机都能听懂英语和普通话,那我大中华几万种方言的被智能化也许也是趋势,我们的方言虽然和普通话相似,但是还是不一样的。这可能需要一个新的语法分析器来帮助我们。
我们的解释器模式就是描述了如何为简单的语言定义一个文法,如何在该语言中表示一个句子,以及如何解释这些句子。
但是解释一门自然语言是复杂的过程,我们以加减运算为例子来详细解释解释器模式。
类图和实例:
抽象表达式角色(AbstractExpression): 声明一个抽象的解释操作,这个接口为所有具体表达式角色都要实现的。
终结符表达式角色(TerminalExpression): 实现与文法中的元素相关联的解释操作,通常一个解释器模式中只有一个终结符表达式,但有多个实例对应不同的终结符,
终结符就是语言中用到的基本元素,一般不能再被分解,如: x -> xa, 这里a是终结符,因为没有别的规则可以把a变成别的符号,不过x可以变成别的符号,所以x是非终结符。
非终结符表达式角色(NonterminalExpression): 文法中的每条规则对应于一个非终结表达式,
非终结表达式根据逻辑的复杂程度而增加,原则上每个文法规则都对应一个非终结符表达式。
环境角色(Context):包含解释器之外的一些全局信息。
实例:
#include <iostream> #include <map> #include <string> using namespace std; class Context { private: map<string, int> valueMap; public: void addValue(string key,int value) { valueMap.insert(std::pair<string,int>(key,value)); } int getValue(string key) { return valueMap[key]; } }; class AbstractExpression { public : virtual int interpreter(Context context) = 0; }; class AddNonterminalExpression : public AbstractExpression { private : AbstractExpression *left; AbstractExpression *right; public: AddNonterminalExpression(AbstractExpression *left, AbstractExpression *right) { this->left = left; this->right = right; } int interpreter(Context context) { return this->left->interpreter(context) + this->right->interpreter(context); } }; class SubtractNonterminalExpression : public AbstractExpression { private : AbstractExpression *left; AbstractExpression *right; public: SubtractNonterminalExpression(AbstractExpression *left, AbstractExpression *right) { this->left = left; this->right = right; } int interpreter(Context context) { return this->left->interpreter(context) - this->right->interpreter(context); } }; class TerminalExpression : public AbstractExpression { private : int i; public : TerminalExpression(int i) { this->i = i; } int interpreter(Context context) { return this->i; } }; int main(){ //a-b+c Context context; context.addValue("a", 7); context.addValue("b", 8); context.addValue("c", 2); SubtractNonterminalExpression *subtractValue = new SubtractNonterminalExpression(new TerminalExpression( context.getValue("a")), new TerminalExpression(context.getValue("b"))); AddNonterminalExpression *addValue = new AddNonterminalExpression(subtractValue, new TerminalExpression( context.getValue("c"))); cout<< addValue->interpreter(context); return 0; } |
适用性:
在以下情况下可以考虑使用解释器模式:
(1) 可以将一个需要解释执行的语言中的句子表示为一个抽象语法树。
(2) 一些重复出现的问题可以用一种简单的语言来进行表达。
(3) 一个语言的文法较为简单。
(4) 执行效率不是关键问题。(注:高效的解释器通常不是通过直接解释抽象语法树来实现的,而是需要将它们转换成其他形式,使用解释器模式的执行效率并不高。)
优缺点:
优点:
(1) 易于改变和扩展文法。由于在解释器模式中使用类来表示语言的文法规则,因此可以通过继承等机制来改变或扩展文法。
(2) 每一条文法规则都可以表示为一个类,因此可以方便地实现一个简单的语言。
(3) 实现文法较为容易。在抽象语法树中每一个表达式节点类的实现方式都是相似的,这些类的代码编写都不会特别复杂,还可以通过一些工具自动生成节点类代码。
(4) 增加新的解释表达式较为方便。如果用户需要增加新的解释表达式只需要对应增加一个新的终结符表达式或非终结符表达式类,原有表达式类代码无须修改,符合“开闭原则”。
缺点:
(1) 对于复杂文法难以维护。在解释器模式中,每一条规则至少需要定义一个类,因此如果一个语言包含太多文法规则,类的个数将会急剧增加,导致系统难以管理和维护,此时可以考虑使用语法分析程序等方式来取代解释器模式。
(2) 执行效率较低。由于在解释器模式中使用了大量的循环和递归调用,因此在解释较为复杂的句子时其速度很慢,而且代码的调试过程也比较麻烦。
总论:
尽量不要在重要模块中使用解释器模式,因为维护困难。在项目中,可以使用脚本语言来代替解释器模式。
十八、迭代器模式(Iterator Pattern)
概述:
在现在的电视机中,我们使用[后一个]和[前一个]按钮可以很方便的换台,当按下[后一个]按钮时,将切换到下一个预置的频道。想象一下在陌生的城市中的旅店中看电视。当改变频道时,重要的不是几频道,而是节目内容。如果对一个频道的节目不感兴趣,那么可以换下一个频道,而不需要知道它是几频道。
这个其实就是我们迭代器模式的精髓:提供一种方法顺序访问一个聚合对象中各个元素,
而又不需暴露该对象的内部表示。
类图和实例:
迭代器模式由以下角色组成:
1.迭代器角色(Iterator):迭代器角色负责定义访问和遍历元素的接口。
2.具体迭代器角色(Concrete Iterator):具体迭代器角色要实现迭代器接口,并要记录遍历中的当前位置。
3.集合角色(Aggregate):集合角色负责提供创建具体迭代器角色的接口。
4.具体集合角色(Concrete Aggregate):具体集合角色实现创建具体迭代器角色的接口——这个具体迭代器角色于该集合的结构相关。
#include <iostream> #include <vector> using namespace std; template<class Item> class Iterator { public: virtual void first()=0; virtual void next()=0; virtual Item* currentItem()=0; virtual bool isDone()=0; virtual ~Iterator(){} }; template<class Item> class ConcreteAggregate; template<class Item> class ConcreteIterator : public Iterator <Item> { ConcreteAggregate<Item> * aggr; int cur; public: ConcreteIterator(ConcreteAggregate<Item>*a):aggr(a),cur(0){} virtual void first() { cur=0; } virtual void next() { if(cur<aggr->getLen()) cur++; } virtual Item* currentItem() { if(cur<aggr->getLen()) return &(*aggr)[cur]; else return NULL; } virtual bool isDone() { return (cur>=aggr->getLen()); } }; template<class Item> class Aggregate { public: virtual Iterator<Item>* createIterator()=0; virtual ~Aggregate(){} }; template<class Item> class ConcreteAggregate:public Aggregate<Item> { vector<Item >data; public: ConcreteAggregate() { data.push_back(1); data.push_back(2); data.push_back(3); } virtual Iterator<Item>* createIterator() { return new ConcreteIterator<Item>(this); } Item& operator[](int index) { return data[index]; } int getLen() { return data.size(); } }; int main() { Aggregate<int> * aggr =new ConcreteAggregate<int>(); Iterator<int> *it=aggr->createIterator(); for(it->first();!it->isDone();it->next()) { cout<<*(it->currentItem())<<endl; } delete it; delete aggr; return 0; } |
实现要点:
1.迭代抽象:访问一个聚合对象的内容而无需暴露它的内部表示。
2.迭代多态:为遍历不同的集合结构提供一个统一的接口,从而支持同样的算法在不同的集合结构上进行操作。
3.迭代器的健壮性考虑:遍历的同时更改迭代器所在的集合结构,会导致问题。
适用性:
1.访问一个聚合对象的内容而无需暴露它的内部表示。
2.支持对聚合对象的多种遍历。
3.为遍历不同的聚合结构提供一个统一的接口(即, 支持多态迭代)。
其他:
1.在C++下可以参看 STL iterators 的实现。
2.在.NET下实现Iterator模式,对于聚集接口和迭代器接口已经存在了,其中IEnumerator扮演的就是迭代器的角色,而IEnumerable则扮演的就是抽象聚集的角色,她只有一个GetEnumerator()方法,如果集合对象需要具备跌代遍历的功能,就必须实现该接口。
3.在Java下可以参看集合类型迭代器(java.util.Iterator,java.util.Enumeration)。
十九、中介者模式(Mediator Pattern)
概述:
假设我们开发一个图片处理软件,里面肯定包括很多相关功能,比如说剪切,旋转,滤镜,美化等等,而我们这些功能所要处理的对象是固定的,就是我们所显示的那张图片。但是我们不能把所有的功能罗列到一个tab上,虽然这样处理方便但是不美观。这是我们可以这样子:用一个中介者类负责所有功能的初始化和具体执行,我们需要功能时直接调用中介者类即可。
中介者模式就是定义一个中介对象来封装系列对象之间的交互。中介者使各个对象不需要显示地相互引用,从而使其耦合性松散,而且可以独立地改变他们之间的交互。
类图和实例
Mediator类:抽象中介者,定义了同事对象交互的接口。
ConcreteMediator类:具体中介者对象,实现抽象类中的方法,此具体中介者对象需要知道所有具体同事类,并从具体同事接受消息,向具体同事对象发送命令。
Colleague类:抽象同事类。
ConcreteColleague类:具体同事类,实现抽象同事类中的方法。每一个同时类需要知道中介者对象;每个具体同事类只需要了解自己的行为,而不需要了解其他同事类的情况。
#include <iostream> #include <vector> #include <string> using namespace std; class Colleage { private: string name; string content; public: Colleage(string n = " "):name(n){}; void set_name(string name) { this->name = name; } string get_name() { return this->name; } void set_content(string content) { this->content = content; } string get_content() { if(content.size() != 0) return content; else return "Copy that"; } virtual void talk(){}; }; class Monitor : public Colleage { public: Monitor(string n = ""):Colleage(n){}; virtual void talk() { cout<<"班长 "<<get_name()<<" 说:"<<get_content()<<endl; } }; class Secretary : public Colleage { public: Secretary(string n = ""):Colleage(n){}; virtual void talk() { cout<<"团支书 "<<get_name()<<" 说:"<<get_content()<<endl; } }; class StudentA : public Colleage { public: StudentA(string n = ""):Colleage(n){}; virtual void talk() { cout<<"学生 A "<<get_name()<<" 说:"<<get_content()<<endl; } }; class StudentB : public Colleage { public: StudentB(string n = ""):Colleage(n){}; virtual void talk() { cout<<"学生 B "<<get_name()<<" 说:"<<get_content()<<endl; } }; class Mediator { public: vector<Colleage*> studentList; virtual void add_student(Colleage *student) { studentList.push_back(student); }; virtual void notify(Colleage *student){}; }; class QQMediator : public Mediator { public: virtual void notify(Colleage *student) { student->talk(); for(int i = 0 ; i < studentList.size() ; ++i) { if(student != studentList[i]) { studentList[i]->talk(); } } }; };int main() { QQMediator qq; Monitor *studentMonitor = new Monitor("Foxx"); Secretary *studentSecretary = new Secretary("TC"); StudentA *studentA = new StudentA("Jack"); StudentB *studentB = new StudentB("Frank"); qq.add_student(studentSecretary); qq.add_student(studentA); qq.add_student(studentB); studentMonitor->set_content("明天开始放假!"); qq.notify(studentMonitor); return 0; } |
适用性:
1.一组对象以定义良好但是复杂的方式进行通信。产生的相互依赖关系结构混乱且难以理解。
2.一个对象引用其他很多对象并且直接与这些对象通信,导致难以复用该对象。
3.想定制一个分布在多个类中的行为,而又不想生成太多的子类。
优缺点:
使用中介者模式的优点:
1.降低了系统对象之间的耦合性,使得对象易于独立的被复用。
2.提高系统的灵活性,使得系统易于扩展和维护。
使用中介者模式的缺点:
由于我们这个“中介“承担了较多的责任,所以一旦这个中介对象出现了问题,那么整个系统就会受到重大的影响。
二十、备忘录模式(Memento Pattern)
概述:
我们玩单机游戏的时候总会遇到老婆大人的各位事情,一会去买瓶醋了,一会去打个酱油了,会耽误我们玩游戏的进程,但是此时我们能有“保存游戏”这个宝贝,我们的主基地不会在我们打酱油的时候被对手拆掉。
这“保存游戏”的功能其实就是备忘录模式的很好应用,她是在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态。这样以后就可以就该对象恢复到原先保存的状态。这个其实也是我们的redo,undo所采用的模式。
类图和实例:
简单的模式实例:
#include <iostream> #include <string> using namespace std; class Memento { private: string state;public: Memento() { state = ""; } Memento(string state){ this->state = state; } string getState() { return state; } void setState(string state) { this->state = state; } };class Originator { private : string state;public: Originator() { state = ""; } string getState() { return state; } void setState(string state) { this->state = state; } Memento createMemento(){ return Memento(this->state); } void restoreMemento(Memento memento){ this->setState(memento.getState()); } };
class Caretaker {
private :
Memento memento;
public :
Memento getMemento(){
return memento;
}
void setMemento(Memento
memento){
this->memento
= memento;
}
};
int main (int argc, char *argv[])
{
Originator originator;
originator.setState("状态1");
cout<<"初始状态:"<<originator.getState()<<endl;
Caretaker caretaker;
caretaker.setMemento(originator.createMemento());
originator.setState("状态2");
cout<<"改变后状态:"<<originator.getState()<<endl;
originator.restoreMemento(caretaker.getMemento());
cout<<"恢复后状态:"<<originator.getState()<<endl;
}
|
适用性:
适用于功能比较复杂的,但需要记录或维护属性历史的类;或者需要保存的属性只是众多属性中的一小部分时Originator可以根据保存的Memo还原到前一状态。
优缺点:
优点:
1)当发起人角色的状态有改变时,有可能是个错误的改变,我们使用备忘录模式就可以把这个错误改变还原。
2)备份的状态是保存在发起人角色之外的,这样,发起人角色就不需要对各个备份的状态进行管理。
缺点:
1)如果备份的对象存在大量的信息或者创建、恢复操作非常频繁,则可能造成很大的性能开销。 |