一、引子
对于系统中一个已经完成的类层次结构,我们已经给它提供了满足需求的接口。但是面对新增加的需求,我们应该怎么做呢?如果这是为数不多的几次变动,而且你不用为了一个需求的调整而将整个类层次结构统统地修改一遍,那么直接在原有类层次结构上修改也许是个不错的主意。
但是往往我们遇到的却是:这样的需求变动也许会不停的发生;更重要的是需求的任何变动可能都要让你将整个类层次结构修改个底朝天……。这种类似的操作分布在不同的类里面,不是一个好现象,我们要对这个结构重构一下了。那么,访问者模式也许是你很好的选择。
二、定义
访问者模式,顾名思义使用了这个模式后就可以在不修改已有程序结构的前提下,通过添加额外的“访问者”来完成对已有代码功能的提升。
《设计模式》一书对于访问者模式给出的定义为:表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。从定义可以看出结构对象是使用访问者模式必须条件,而且这个结构对象必须存在遍历自身各个对象的方法。这便类似于 java 中的 collection 概念了。
三、结构
以下是访问者模式的组成结构:
1) 访问者角色( Visitor):为该对象结构中具体元素角色声明一个访问操作接口。该操作接口的名字和参数标识了发送访问请求给具体访问者的具体元素角色。这样访问者就可以通过该元素角色的特定接口直接访问它。
2) 具体访问者角色( ConcreteVisitor):实现每个由访问者角色( Visitor)声明的操作。
3) 元素角色( Element):定义一个 Accept操作,它以一个访问者为参数。
4) 具体元素角色( ConcreteElement):实现由元素角色提供的 Accept 操作。
5) 对象结构角色( ObjectStructure):这是使用访问者模式必备的角色。它要具备以下特征:能枚举它的元素;可以提供一个高层的接口以允许该访问者访问它的元素;可以是一个复合(组合模式)或是一个集合,如一个列表或一个无序集合。
四、实现
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class ConcreteElementA;
class ConcreteElementB;
//访问者角色( Visitor):为该对象结构中具体元素角色声明一个访问操作接口
class Visitor
{
public:
virtual void VisitConcreteElementA(ConcreteElementA *pElementA) = 0;
virtual void VisitConcreteElementB(ConcreteElementB *pElementB) = 0;
};
//具体访问者角色( Concrete Visitor):实现每个由访问者角色( Visitor)声明的操作
class ConcreteVisitor1 : public Visitor
{
public:
void VisitConcreteElementA(ConcreteElementA *pElementA);
void VisitConcreteElementB(ConcreteElementB *pElementB);
};
void ConcreteVisitor1::VisitConcreteElementA(ConcreteElementA *pElementA)
{
// 现在根据传进来的pElementA,可以对ConcreteElementA中的element进行操作
}
void ConcreteVisitor1::VisitConcreteElementB(ConcreteElementB *pElementB)
{
// 现在根据传进来的pElementB,可以对ConcreteElementB中的element进行操作
}
class ConcreteVisitor2 : public Visitor
{
public:
void VisitConcreteElementA(ConcreteElementA *pElementA);
void VisitConcreteElementB(ConcreteElementB *pElementB);
};
void ConcreteVisitor2::VisitConcreteElementA(ConcreteElementA *pElementA)
{
// ...
}
void ConcreteVisitor2::VisitConcreteElementB(ConcreteElementB *pElementB)
{
// ...
}
//元素角色( Element):定义一个 Accept 操作,它以一个访问者为参数。
class Element
{
public:
virtual void Accept(Visitor *pVisitor) = 0;
};
//具体元素角色( Concrete Element):实现由元素角色提供的 Accept 操作
class ConcreteElementA : public Element
{
public:
void Accept(Visitor *pVisitor);
};
void ConcreteElementA::Accept(Visitor *pVisitor)
{
pVisitor->VisitConcreteElementA(this);
}
class ConcreteElementB : public Element
{
public:
void Accept(Visitor *pVisitor);
};
void ConcreteElementB::Accept(Visitor *pVisitor)
{
pVisitor->VisitConcreteElementB(this);
}
// 对象结构角色:能枚举它的元素,可以提供一个高层的接口以允许访问者访问它的元素
class ObjectStructure
{
public:
void Attach(Element *pElement);
void Detach(Element *pElement);
void Accept(Visitor *pVisitor);
private:
vector<Element *> elements;
};
void ObjectStructure::Attach(Element *pElement)
{
elements.push_back(pElement);
}
void ObjectStructure::Detach(Element *pElement)
{
vector<Element *>::iterator it = find(elements.begin(), elements.end(), pElement);
if (it != elements.end())
{
elements.erase(it);
}
}
void ObjectStructure::Accept(Visitor *pVisitor)
{
// 为每一个element设置visitor,进行对应的操作
for (vector<Element *>::const_iterator it = elements.begin(); it != elements.end(); ++it)
{
(*it)->Accept(pVisitor);
}
}
int main()
{
ObjectStructure *pObject = new ObjectStructure;
ConcreteElementA *pElementA = new ConcreteElementA;
ConcreteElementB *pElementB = new ConcreteElementB;
pObject->Attach(pElementA);
pObject->Attach(pElementB);
ConcreteVisitor1 *pVisitor1 = new ConcreteVisitor1;
ConcreteVisitor2 *pVisitor2 = new ConcreteVisitor2;
pObject->Accept(pVisitor1);
pObject->Accept(pVisitor2);
if (pVisitor2) delete pVisitor2;
if (pVisitor1) delete pVisitor1;
if (pElementB) delete pElementB;
if (pElementA) delete pElementA;
if (pObject) delete pObject;
return 0;
}
五、总结
访问者模式的基本思想如下:首先拥有一个由许多对象构成的对象结构,就是上面代码中的ObjectStructure,这些对象的类都拥有一个Accept方法用来接受访问者对象;访问者是一个接口,它拥有一个Visit方法,这个方法对访问到的对象结构中不同类型的元素做出不同的操作;在对象结构的一次访问过程中,我们遍历整个对象结构,对每一个元素都实施Accept方法,在每一个元素的Accept方法中回调访问者的Visit方法,从而使访问者得以处理对象结构的每一个元素。我们就可以针对对象结构设计不同的访问者类来完成不同的操作。
设计模式中经常说的一句话是:发现变化并封装之。是否采用访问者模式,就要看“变化”是什么。访问者模式中,“变化”是具体访问者,其次是对象结构;但是,如果具体元素也会发生改变,就万万不能使用访问者模式,因为这样“牵一发而动全身”,后期的维护性就太差了。