面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(Requirement Changing),经常做好的一个设计、实现了一个系统原型,客户又会有了新的需求。因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
Visitor模式表示一个作用于某对象结构中的各元素的操作.它使我们可以在不改变各元素的类
的前提下定义作用于这些元素的新操作.
Visitor模式把对结点的访问封装成一个抽象基类,通过派生出不同的类生成新的访问方式.实现时,在visitor抽象基类中声明了对所有不同结点进行访问的接口函数。Visitor模式的有一个缺陷--新加入一个结点时都要添加Visitor中的对其进行访问接口函数,这样使得所有的Visitor及其派生类都要重新编译了,也就是说Visitor模式一个缺点就是添加新的结点十分困难.另外,还需要指出的是Visitor模式采用了所谓的"双重分派"的技术。
Visitor模式在不破坏类的前提下,为类提供增加新的新操作。Visitor模式的关键是双分派(Double-Dispatch)的技术。C++语言支持的是单分派。
#ifndef VISITOR_H
#define VISITOR_H
class Visitor;
class Element
{
public:
virtual ~Element(){}
virtual void Accept(Visitor &rVisitor) = 0;
protected:
Element(){}
};
class ConcreateElementA : public Element
{
public:
virtual ~ConcreateElementA() {}
virtual void Accept(Visitor &rVisitor);
};
class ConcreateElementB : public Element
{
public:
virtual ~ConcreateElementB() {}
virtual void Accept(Visitor &rVisitor);
};
class Visitor
{
public:
virtual ~Visitor(){}
virtual void VisitConcreateElementA(ConcreateElementA *pConcreateElement
A) = 0;
virtual void VisitConcreateElementB(ConcreateElementB *pConcreateElement
B) = 0;
protected:
Visitor(){}
};
class ConcreateVisitorA : public Visitor
{
public:
virtual ~ConcreateVisitorA(){}
virtual void VisitConcreateElementA(ConcreateElementA *pConcreateElement
A);
virtual void VisitConcreateElementB(ConcreateElementB *pConcreateElement
B);
};
class ConcreateVisitorB : public Visitor
{
public:
virtual ~ConcreateVisitorB(){}
virtual void VisitConcreateElementA(ConcreateElementA *pConcreateElement
A);
virtual void VisitConcreateElementB(ConcreateElementB *pConcreateElement
B);
};
#endif
#include "Visitor.h"
#include <iostream>
void ConcreateElementA::Accept(Visitor &rVisitor)
{
rVisitor.VisitConcreateElementA(this);
}
void ConcreateElementB::Accept(Visitor &rVisitor)
{
rVisitor.VisitConcreateElementB(this);
}
void ConcreateVisitorA::VisitConcreateElementA(ConcreateElementA *pConcreat
eElementA)
{
std::cout << "VisitConcreateElementA By ConcreateVisitorA/n";
}
void ConcreateVisitorA::VisitConcreateElementB(ConcreateElementB *pConcreat
eElementA)
{
std::cout << "VisitConcreateElementB By ConcreateVisitorA/n";
}
void ConcreateVisitorB::VisitConcreateElementA(ConcreateElementA *pConcreat
eElementA)
{
std::cout << "VisitConcreateElementA By ConcreateVisitorB/n";
}
void ConcreateVisitorB::VisitConcreateElementB(ConcreateElementB *pConcreat
eElementA)
{
td::cout << "VisitConcreateElementB By ConcreateVisitorB/n";
}
#include "Visitor.h"
int main()
{
Visitor *pVisitorA = new ConcreateVisitorA();
Element *pElement = new ConcreateElementA();
pElement->Accept(*pVisitorA);
delete pElement;
delete pVisitorA;
return 0;
}
在Visitor模式中Accept()操作是一个双分派的操作。具体调用哪一个具体的Accept()操作,有两个决定因素:1)Element的类型。因为Accept()是多态的操作,需要具体的Element类型的子类才可以决定到底调用哪一个Accept()实现;2)Visitor的类型。Accept()操作有一个参数(Visitor* vis),要决定了实际传进来的Visitor的实际类别才可以决定具体是调用哪个VisitConcrete()实现。
Visitor模式的实现过程中有以下的地方要注意:
Visitor类中的Visit()操作的实现。这里我们可以向Element类仅仅提供一个接口Visit(),而在Accept()实现中具体调用哪一个Visit()操作则通过函数重载(overload)的方式实现:我们提供Visit()的两个重载版本:a)Visit(ConcreteElementA* elmA),b)Visit(ConcreteElementB* elmB)。
C++中还可以通过RTTI(运行时类型识别:Runtime type identification)来实现,即我们只提供一个Visit()函数体,传入的参数为Element*型别参数 ,然后用RTTI决定具体是哪一类的ConcreteElement参数,再决定具体要对哪个具体类施加什么样的具体操作。RTTI给接口带来了简单一致性,但是付出的代价是时间(RTTI的实现)和代码的Hard编码(要进行强制转换)。
Visitor模式可以使得Element在不修改自己的同时增加新的操作,但是这也带来了至少以下的两个显著问题:
1) 破坏了封装性。Visitor模式要求Visitor可以从外部修改Element对象的状态,这一般通过两个方式来实现:a)Element提供足够的public接口,使得Visitor可以通过调用这些接口达到修改Element状态的目的;b)Element暴露更多的细节给Visitor,或者让Element提供public的实现给Visitor(当然也给了系统中其他的对象),或者将Visitor声明为Element的friend类,仅将细节暴露给Visitor。但是无论那种情况,特别是后者都将是破坏了封装性原则。
2) ConcreteElement的扩展很困难:每增加一个Element的子类,就要修改Visitor的接口,使得可以提供给这个新增加的子类的访问机制。从上面我们可以看到,或者增加一个用于处理新增类的Visit()接口,或者重载一个处理新增类的Visit()操作,或者要修改RTTI方式实现的Visit()实现。无论那种方式都给扩展新的Element子类带来了困难。