Visitor设计模式

面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(Requirement Changing),经常做好的一个设计、实现了一个系统原型,客户又会有了新的需求。因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。
 
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
 
Visitor模式经常用于将更新的设计封装在一个类中,并且由待更改的类提供一个接受接口,其关键技术在于双分派技术,如图所示,Element类提供接口,通过Accept实现具体使用哪一个具体的Visit操作;
    当然如果有很多的修改,便可以提供更多的Element的Visitor,但是会破坏系统的封装,并且难于扩展。
     !!! 也就是说,如果一组类想增加一个操作,那么,先在基类中定义一个virtual Accept(visite&)接口,然后,子类重载该该accept.指定具体的visitor的指定操作.为该组类定义一个visitor基类,针对具体某个类的改变要么各个派生一个visit要么,只派生一个visit,往其中加入对应于不同的类操作的函数. 也就是说.你也可以,把对多个类的更改都集中于一个Visitor中.在Visitor中多提供几个接口eg VisiteElemetA() VisitElemetB().VisiteElementC()....
       再提醒一次,对一个类执行多个操作时,必须从visitor中派生出多个Visit!!!.对多个类执行一个操作.可用一个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子类带来了困难。

 
点评:
文中说 Accept()是一个双分派的操作.但想一下,C++是不支持双分派的.这不是矛盾吗?
事实上,说C++不支持双分派,仅当出现两个同名函数.仅参数不一样,(差别在于一个是基类指针,一个派生类指针)时,才表现出不支持double dispatch.正常情况下,C++会表现出你所期望的那样.本例中,Element::Accept()仅有一个同名函数,所以,不存在double diapatch()之说.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值