访问者模式(Visitor Pattern)的c++实现示例
访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类或类群(即被访问者)增加新的操作(即访问者)而无需为它们作任何修改。访问者模式属于行为型模式。
为什么要使用访问者模式?
如何扩展一个现有的类层次结构来实现新行为?一般的方法是给类添加新的方法。但是万一新行为和现有对象模型不兼容怎么办?还有,类层次结构设计人员可能无法预知以后开发过程中将会需要哪些功能。以及,如果已有的类层次结构不允许修改代码,怎么能扩展行为呢?
答案是在类层次结构设计中使用访问者模式。
访问者模式涉及的角色:
1)访问者(Visitor)
访问者抽象接口,通过visit(Element)方法访问Element(数据结构),完成对Element的操作行为。
2)具体访问者(ConcreteVisitor)
访问者的具体实现类。
3)元素(Element),也就是被访问者
通过accept(Visitor)方法接受Visitor的访问。
4)具体元素(ConcreteElement)
元素的具体实现类。
5)对象结构(ObjectStructure)
拥有一组元素的组合对象。ObjectStructure本身也可以作为被访问者。
访问者模式的结构图(网上下载的):
当一个应用满足以下条件时,我们可以使用Visitor设计模式:
1)一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作。
2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而你想避免让这些操作“污染”这些对象的类。Visitor使得你可以将相关的操作集中起来定义在一个类中。
3) 当该对象结构被很多应用共享时,用Visitor模式让每个应用仅包含需要用到的操作。
4) 定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要很大的代价。如果对象结构类经常改变,那么可能还是在这些类中定义这些操作较好。
优点:
1)易于添加那些目前尚未考虑到的方法。(这也是使用访问者的原因:扩展功能)
2)可以使类更加小巧,因为那些很少使用的方法,可以在外部定义。(意味着如果一个方法经常使用,最好定义在类中;当然在第一次定义中没有考虑到此方法除外)
缺点:
1)访问者角色不适合具体元素角色经常发生变化的情况。(如:增加新具体元素类,访问者接口就需要改变了。)
2)访问者角色要执行与元素角色相关的操作,就必须让元素角色将自己内部属性暴露出来,这就破坏了元素角色的封装性。访问者和被访问的对象的耦合性很大。
3)元素与访问者之间能够传递的信息有限,这往往也会限制访问者模式的使用。(因为访问者不能直接访问元素的私有数据)
示例:
- #include <iostream>
- #include <list>
- #include <string>
- using namespace std;
- class CPerson;
- class CStudent;
- class CTeacher;
- class CVisitor;
- class CPrinter;
- //元素(被访问者)
- class CPerson //纯虚类
- {
- protected:
- string name; //或者改成数组
- int gender;
- CPerson()
- {
- }
- public:
- virtual void Accept( CVisitor& ) //= 0;//纯虚函数
- {
- }
- public:
- void SetName(const string& Name)
- {
- name = Name;
- }
- string GetName() const
- {
- return name;
- }
- void SetGender(const int& Gender)
- {
- gender = Gender;
- }
- int GetGender() const
- {
- return gender;
- }
- };
- //访问者
- class CVisitor
- {
- public:
- virtual void Visit( CStudent& ) = 0;
- virtual void Visit( CTeacher& ) = 0;
- };
- //具体元素
- class CStudent: public CPerson
- {
- private:
- int grade; //年级
- public:
- CStudent(string Name,int Gender,int Grade)
- {
- name=Name;
- gender=Gender;
- grade=Grade;
- }
- public:
- virtual void Accept(CVisitor& printer) //虚函数
- {
- printer.Visit(*this);
- }
- void SetGrade(int Grade)
- {
- grade=Grade;
- }
- int GetGrade() const
- {
- return grade;
- }
- };
- //具体元素
- class CTeacher:public CPerson
- {
- private:
- int service_time;//工龄
- public:
- CTeacher(string Name,int Grade, int ServiceTime)
- {
- name = Name;
- gender = Grade;
- service_time = ServiceTime;
- }
- public:
- virtual void Accept(CVisitor& printer)
- {
- printer.Visit(*this);
- }
- void SetServiceTime(int ServiceTime)
- {
- service_time = ServiceTime;
- }
- int GetServiceTime() const
- {
- return service_time;
- }
- };
- //具体访问者
- //这里,这个类打印具体元素的信息(就是显示在屏幕上),可根据需要打印不同的内容。
- //具体访问者是一种策略,可根据不同需要创建新的具体访问者,而无需修改具体元素(即被访问者)。
- class CPrinter: public CVisitor
- {
- public:
- void Visit(CStudent& s)
- {
- cout <<"student:" <<endl;
- cout <<"/t Name:" <<s.GetName() <<endl;
- if(s.GetGender()==0)
- cout <<"/t Gender:" <<"female" <<endl;
- else
- cout <<"/t Gender:" <<"male" <<endl;
- cout <<"/t Grade:" <<s.GetGrade() <<endl;
- }
- void Visit(CTeacher& t)
- {
- cout <<"Teacher:" <<endl;
- cout <<"/t Name:" <<t.GetName() <<endl;
- if(t.GetGender()==0)
- cout <<"/t Gender:" <<"female" <<endl;
- else
- cout <<"/t Gender:" <<"male" <<endl;
- cout <<"/t Service Time:" <<t.GetServiceTime()<<endl;
- }
- };
- //对象结构
- //这里,Organization是个组织(如植树节组成一个团队,去植树),有若干老师,若干学生。
- class Organization
- {
- private:
- typedef list<CPerson*> CMemberList;
- CMemberList member_list;
- public:
- void Add(CPerson* person) //增加人员。只是示例,没有考虑人员重复等情况。
- {
- //注意:person指向的空间必须是由new操作符申请的空间
- member_list.push_back(person);
- }
- //只是示例,所以删除成员等操作略去
- void PrintMembers(CPrinter& printer) //输出成员名单
- {
- CMemberList::iterator itr = member_list.begin();
- for(; itr != member_list.end(); ++itr)
- {
- (*itr)->Accept(printer);
- }
- }
- ~Organization()
- {
- //删除申请的空间;c++比较麻烦
- CMemberList::iterator itr = member_list.begin();
- for(; itr != member_list.end(); ++itr)
- {
- delete *itr;
- }
- }
- };
- int main()
- {
- Organization Planting;//植树组织
- Planting.Add( new CTeacher("Johnny",1,10) );
- Planting.Add( new CStudent("Catherine",0,1) );
- Planting.Add( new CStudent("peter",1,2) );
- CPrinter printer;//访问者,也可看成一种策略
- Planting.PrintMembers(printer);
- return 0;
- }
后话:
使用了访问者模式以后,对于原来的类层次增加新的操作,仅仅需要实现一个具体访问者角色就可以了,而不必修改整个类层次。而且这样符合“开闭原则”的要求。而且每个具体的访问者角色都对应于一个相关操作,因此如果一个操作的需求有变,那么仅仅修改一个具体访问者角色,而不用改动整个类层次。但是“开闭原则”的遵循总是片面的。如果系统中的类层次发生了变化,会对访问者模式产生什么样的影响呢?你必须修改访问者角色和每一个具体访问者角色。
模式设计教材书经常提及的一句话:发现变化并封装之。是否采用访问者模式,就要看(或预见)“变化”是什么了。访问者模式中,“变化”是主要是具体访问者,其次是对象结构。但如果(具体)元素也改变,就万万不能用访问者模式,因为“牵一发动全身”,维护性就太差了。
再唠叨一下为什么使用访问者模式的个人看法:
对象结构使用的了(具体)元素(即被访问者),而(具体)元素的功能不全,而直接在元素中添加功能不太好(如:不允许修改元素;或者增加不常用的功能会使类太臃肿等等),那么我们就定一个辅助类(就是访问者)来完成这个功能。于是,作为预见性,我们为元素(即被访问者)增加一个accept(Visitor)方法作为接口,以作不时之需。为元素增加新的功能,只需增加新的访问者类。
参考文献:
1.《设计模式初学者指南》,徐迎晓等译,机械工业出版社
2.网上,东抄一下,西抄一下。