访问者模式是一种分离对象数据结构与行为的方法,通过这种分离,可以为一个已存在的类或类群(即被访问者)增加新的操作(即访问者)而无需为它们作任何修改。访问者模式属于行为型模式。
为什么要使用访问者模式?
如何扩展一个现有的类层次结构来实现新行为?一般的方法是给类添加新的方法。但是万一新行为和现有对象模型不兼容怎么办?还有,类层次结构设计人员可能无法预知以后开发过程中将会需要哪些功能。以及,如果已有的类层次结构不允许修改代码,怎么能扩展行为呢?
答案是在类层次结构设计中使用访问者模式。
访问者模式涉及的角色:
1)访问者(Visitor)
访问者抽象接口,通过visit(Element)方法访问Element(数据结构),完成对Element的操作行为。
2)具体访问者(ConcreteVisitor)
访问者的具体实现类。
3)元素(Element),也就是被访问者
通过accept(Visitor)方法接受Visitor的访问。
4)具体元素(ConcreteElement)
元素的具体实现类。
5)对象结构(ObjectStructure)
拥有一组元素的组合对象。ObjectStructure本身也可以作为被访问者。
我在这里写了一个访问者模式的应用,作为多灾多难的四川人民在地震后感谢全国人民的鼎力相援。
假设这里Visitor是捐助者,Element是灾区人民,ObejectSturcutre是政府组织救灾物资的发放假设各灾区收到的物资一样,那么访问者模式在这里就是有各种各样的捐助者捐助不同的东西到灾区,政府将这些东西均等的发放到灾区,最后灾区人民感谢捐助者。
#include<iostream>
#include<string>
#include<list>
using namespace std;
class Element;//灾区基类
class Visitor{//捐助者基类
public:
virtual void visit(Element*)=0;//捐助方法
Visitor(int x=0):num(x){}
public:
int num;//捐助数量
};
class Element{
public:
virtual void accept(Visitor*)=0;//接受捐助方法
Element(string s="四川"):name(s){}
public:
string name;//灾区名字
};
class ConcreteElement:public Element{
public:
void accept(Visitor* v){
v->visit(this);//调用不同捐助者特有的捐助方法
}
ConcreteElement(string s="四川"){name=s;}
};
class Visitor_money:public Visitor{
public:
Visitor_money(int m=100){num=m;}
void visit(Element* e){//钱财捐助者的捐助方法
cout<<e->name<<"人民感谢您捐助钱财"<<num<<"元"<<endl;
}
};
class Visitor_book:public Visitor{
public:
Visitor_book(int b=10){num=b;}
void visit(Element* e){//捐助书籍者的捐助方法
cout<<e->name<<"人民感谢您捐助书籍"<<num<<"册"<<endl;
}
};
class ObjectStructure{//救灾物资统筹者,这里假设是政府
private:
list<ConcreteElement*> vec;//灾区队列
public:
void attach(ConcreteElement* x){
vec.push_back(x);//添加灾区
}
void detach(ConcreteElement* x){
vec.remove(x);//移除灾区
}
void accept(Visitor* v){
for(list<ConcreteElement*>::iterator it=vec.begin();it!=vec.end();it++){
(*it)->accept(v);//政府统筹下每个灾区都接受捐助者v相同数量的物资
}
}
};
int main(){
ObjectStructure goverment;
goverment.attach(new ConcreteElement("汶川"));
goverment.attach(new ConcreteElement("雅安"));
Visitor_money* v_m=new Visitor_money(1000000);
Visitor_book* v_b=new Visitor_book(100000);
goverment.accept(v_m);
goverment.accept(v_b);
return 0;
}
输出:
访问者模式的优点
- 符合单一职责原则:凡是适用访问者模式的场景中,元素类中需要封装在访问者中的操作必定是与元素类本身关系不大且是易变的操作,使用访问者模式一方面符合单一职责原则,另一方面,因为被封装的操作通常来说都是易变的,所以当发生变化时,就可以在不改变元素类本身的前提下,实现对变化部分的扩展。
- 扩展性良好:元素类可以通过接受不同的访问者来实现对不同操作的扩展。
访问者模式的适用场景
假如一个对象中存在着一些与本对象不相干(或者关系较弱)的操作,为了避免这些操作污染这个对象,则可以使用访问者模式来把这些操作封装到访问者中去。
假如一组对象中,存在着相似的操作,为了避免出现大量重复的代码,也可以将这些重复的操作封装到访问者中去。
但是,访问者模式并不是那么完美,它也有着致命的缺陷:增加新的元素类比较困难。通过访问者模式的代码可以看到,在访问者类中,每一个元素类都有它对应的处理方法,也就是说,每增加一个元素类都需要修改访问者类(也包括访问者类的子类或者实现类),修改起来相当麻烦。也就是说,在元素类数目不确定的情况下,应该慎用访问者模式。所以,访问者模式比较适用于对已有功能的重构,比如说,一个项目的基本功能已经确定下来,元素类的数据已经基本确定下来不会变了,会变的只是这些元素内的相关操作,这时候,我们可以使用访问者模式对原有的代码进行重构一遍,这样一来,就可以在不修改各个元素类的情况下,对原有功能进行修改。