分享

C++ visitor模式

 WUCANADA 2013-06-26

C++访问者模式

由遇到的问题引出访问者模式

在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(Requirement Changing),经常我们做好的一个设计、实现了一个系 统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需 求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。

访问者模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。

模式选择

我们通过访问者模式解决上面的问题,其典型的结构图为:

图 2-1:访问者模式结构图

访问者模式在不破坏类的前提下,为类提供增加新的新操作。访问者模式的关键是双分派(Double-Dispatch)的技术【注释 1】。C++语言支持的是单分派。在访问者模式中 Accept()操作是一个双分派的操作。具体调用哪一个具体的 Accept()操作,有两个决定因素:1)Element 的类型。因为 Accept()是多态的操作,需要具体的 Element 类型的子类才可以决定到底调用哪一个 Accept()实现;2)访问者的类型。

Accept()操作有一个参数(Visitor* vis),要决定了实际传进来的访问者的实际类别才可以决定具体是调用哪个 VisitConcrete()实现。

访问者模式的实现

完整代码示例(code):访问者模式的实现很简单,这里为了方便初学者的学习和参考,将给出完整的实现代码(所有代码采用 C++实现,并在 VC 6.0 下测试运行)。
【注释 1】:双分派意味着执行的操作将取决于请求的种类和接收者的类型。更多资料请参考资料。

代码片断 1:Visitor.h
//Visitor.h
#ifndef _VISITOR_H_
#define _VISITOR_H_
class ConcreteElementA;
class ConcreteElementB;
class Element;
class Visitor{
    public:
    virtual ~Visitor();
    virtual void VisitConcreteElementA(Element* elm) = 0;
    virtual void VisitConcreteElementB(Element* elm) = 0;
    protected:
    Visitor();
    private:
};
class ConcreteVisitorA:public Visitor{
    public:
    ConcreteVisitorA();
    virtual ~ConcreteVisitorA();
    virtual void VisitConcreteElementA(Element* elm);
    virtual void VisitConcreteElementB(Element* elm);
    protected:
    private:
};
class ConcreteVisitorB:public Visitor{
    public:
    ConcreteVisitorB();
    virtual ~ConcreteVisitorB();
    virtual void VisitConcreteElementA(Element* elm);
    virtual void VisitConcreteElementB(Element* elm);
    protected:
    private:
};
#endif //~_VISITOR_H_

代码片断 2:Visitor.cpp
//Visitor.cpp
#include "Visitor.h"
#include "Element.h"
#include <iostream>
using namespace std;
Visitor::Visitor(){
}
Visitor::~Visitor(){
}
ConcreteVisitorA::ConcreteVisitorA(){
}
ConcreteVisitorA::~ConcreteVisitorA(){
}
void ConcreteVisitorA::VisitConcreteElementA(Element* elm){
    cout<<"i will visit ConcreteElementA..."<<endl;
}
void ConcreteVisitorA::VisitConcreteElementB(Element* elm){
    cout<<"i will visit ConcreteElementB..."<<endl;
}
ConcreteVisitorB::ConcreteVisitorB(){
}
ConcreteVisitorB::~ConcreteVisitorB(){
}
void ConcreteVisitorB::VisitConcreteElementA(Element* elm){
    cout<<"i will visit ConcreteElementA..."<<endl;
}
void ConcreteVisitorB::VisitConcreteElementB(Element* elm){
    cout<<"i will visit ConcreteElementB..."<<endl;
}

代码片断 3:Template.cpp
//Element.h
#ifndef _ELEMENT_H_
#define _ELEMENT_H_
class Visitor;
class Element{
    public:
    virtual ~Element();
    virtual void Accept(Visitor* vis) = 0;
    protected:
    Element();
    private:
};
class ConcreteElementA:public Element{
    public:
    ConcreteElementA();
    ~ConcreteElementA();
    void Accept(Visitor* vis);
    protected:
    private:
};
class ConcreteElementB:public Element{
    public:
    ConcreteElementB();
    ~ConcreteElementB();
    void Accept(Visitor* vis);
    protected:
    private:
};
#endif //~_ELEMENT_H_

代码片断 4:Element.cpp
//Element.cpp
#include "Element.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
Element::Element(){
}
Element::~Element(){
}
void Element::Accept(Visitor* vis){
}
ConcreteElementA::ConcreteElementA(){
}
ConcreteElementA::~ConcreteElementA(){
}
void ConcreteElementA::Accept(Visitor* vis){
    vis->VisitConcreteElementA(this);
    cout<<"visiting ConcreteElementA..."<<endl;
}
ConcreteElementB::ConcreteElementB(){
}
ConcreteElementB::~ConcreteElementB(){
}
void ConcreteElementB::Accept(Visitor* vis){
    cout<<"visiting ConcreteElementB..."<<endl;
    vis->VisitConcreteElementB(this);
}

代码片断 5:main.cpp
#include "Element.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
int main(int argc,char* argv[]){
    Visitor* vis = new ConcreteVisitorA();
    Element* elm = new ConcreteElementA();
    elm->Accept(vis);
    return 0;
}

代码说明:访问者模式的实现过程中有以下的地方要注意:
1)访问者类中的 Visit()操作的实现。这里我们可以向 Element 类仅仅提供一个接口 Visit(),而在 Accept()实现中具体调用哪一个 Visit()操作则通过函数重载(overload)的方式实现:我们提供 Visit()的两个重载版本 a)Visit(ConcreteElementA* elmA),b)Visit(ConcreteElementB* elmB)。

在 C++中我们还可以通过 RTTI(运行时类型识别:Runtime type identification)来实现,即我们只提供一个 Visit()函数体,传入的参数为 Element*型别参数 ,然后用 RTTI 决定具体是哪一类的 ConcreteElement 参数,再决定具体要对哪个具体类施加什么样的具体操作。【注释 2】RTTI 给接口带来了简单一致性,但是付出的代价是时间(RTTI 的实现)和代码的 Hard 编码(要进行强制转换)。

关于访问者模式的讨论

有时候我们需要为 Element 提供更多的修改,这样我们就可以通过为 Element 提供一系列的访问者模式可以使得 Element 在不修改自己的同时增加新的操作,但是这也带来了至少以下的两个显著问题:
  1. 破坏了封装性。访问者模式要求访问者可以从外部修改 Element 对象的状态,这一般通过两个方式来实现:
    • Element 提供足够的 public 接口,使得访问者可以通过调用这些接口达到修改 Element 状态的目的;
    • Element 暴露更多的细节给 Visitor,或者让 Element 提供 public 的实现给 Visitor(当然也给了系统中其他的对象),或者将访问者声明为 Element 的 friend 类,仅将细节暴露给 Visitor。但是无论那种情况,特别是后者都将是破坏了封装性原则(实际上就是 C++的 friend 机制得到了很多的面向对象专家的诟病)。
  2. ConcreteElement 的扩展很困难:每增加一个 Element 的子类,就要修改访问者的接口,使得可以提供给这个新增加的子类的访问机制。从上面我们可以看到,或者增加一个用于处理新增类的 Visit()接口,或者重载一个处理新增类的 Visit()操作,或者要修改 RTTI 方式实现的 Visit()实现。无论那种方式都给扩展新的 Element子类带来了困难。

    本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
    转藏 分享 献花(0

    0条评论

    发表

    请遵守用户 评论公约

    类似文章 更多