0 引言
0.1 目的
本文档给出设计模式之——Visitor模式的简化诠释,并给出其C++实现。
0.2 说明
| Project | Design Pattern Explanation(By K_Eckel) | 
| Authorization | Free Distributed but Ownership Reserved | 
| Date |  | 
| Test Bed | MS Visual C++ 6.0 | 
0.3 参考
在本文档的写作中,参考了以下的资源,在此列出表示感谢:
u 书籍
[GoF 2000]:GoF,Design Patterns-Elements of Reusable Object-Oriented Software Addison-Wesley 2000/9.
[Martine 2003]:Robert C.Martine, Agile Software Development Principles, Patterns, and Practices, Pearson Education, 2003.
0.4 联系作者
| Author | K_Eckel | 
| State | Candidate for Master’s Degree School of  | 
| E_mail | 
2 Visitor模式
2.1 问题
在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(Requirement Changing),经常我们做好的一个设计、实现了一个系统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
2.2 模式选择
我们通过Visitor模式解决上面的问题,其典型的结构图为:

图2-1:Visitor Pattern结构图
Visitor模式在不破坏类的前提下,为类提供增加新的新操作。Visitor模式的关键是双分派(Double-Dispatch)的技术【注释1】。C++语言支持的是单分派。
在Visitor模式中Accept()操作是一个双分派的操作。具体调用哪一个具体的Accept()操作,有两个决定因素:1)Element的类型。因为Accept()是多态的操作,需要具体的Element类型的子类才可以决定到底调用哪一个Accept()实现;2)Visitor的类型。Accept()操作有一个参数(Visitor* vis),要决定了实际传进来的Visitor的实际类别才可以决定具体是调用哪个VisitConcrete()实现。
【注释1】:双分派意味着执行的操作将取决于请求的种类和接收者的类型。更多资料请参考资料。
2.3 实现
2.3.1  完整代码示例(code)
       Visitor模式的实现很简单,这里为了方便初学者的学习和参考,将给出完整的实现代码(所有代码采用C++实现,并在VC 6.0下测试运行)。
  
| 代码片断1:Visitor.h //Visitor.h #ifndef _VISITOR_H_ #define _VISITOR_H_ 
 
 
 
 { public:       virtual ~Visitor(); virtual void VisitConcreteElementA(Element* elm) = 0;       virtual void VisitConcreteElementB(Element* elm) = 0; protected:       Visitor(); private: }; 
 { public:       ConcreteVisitorA();       virtual ~ConcreteVisitorA();       virtual void VisitConcreteElementA(Element* elm);       virtual void VisitConcreteElementB(Element* elm); protected: private: }; 
 { public:       ConcreteVisitorB();       virtual ~ConcreteVisitorB();       virtual void VisitConcreteElementA(Element* elm);       virtual void VisitConcreteElementB(Element* elm); protected: private: }; | 
| 代码片断2:Visitor.cpp //Visitor.cpp #include "Visitor.h" #include "Element.h" 
 using namespace std; 
 { } 
 { } 
 { } 
 { } 
 {       cout<<"i will visit ConcreteElementA..."<<endl; } 
 {       cout<<"i will visit ConcreteElementB..."<<endl; } 
 { } 
 { } 
 {       cout<<"i will visit ConcreteElementA..."<<endl; } 
 {       cout<<"i will visit ConcreteElementB..."<<endl; } | 
| 代码片断3:Template.cpp //Element.h #ifndef _ELEMENT_H_ #define _ELEMENT_H_ 
 
 { public:       virtual ~Element();       virtual void Accept(Visitor* vis) = 0; protected:       Element(); private: }; 
 { public:       ConcreteElementA();       ~ConcreteElementA();       void Accept(Visitor* vis); protected: private: }; 
 { public:       ConcreteElementB();       ~ConcreteElementB();       void Accept(Visitor* vis); protected: private: }; | 
| 代码片断4:Element.cpp //Element.cpp #include "Element.h" #include "Visitor.h" 
 using namespace std; 
 { } 
 { } 
 { } 
 { } 
 { } 
 {       vis->VisitConcreteElementA(this);       cout<<"visiting ConcreteElementA..."<<endl; } 
 { } 
 { } 
 {       cout<<"visiting ConcreteElementB..."<<endl;       vis->VisitConcreteElementB(this); } | 
| 代码片断5:main.cpp #include "Element.h" #include "Visitor.h" 
 using namespace std; 
 {       Visitor* vis = new ConcreteVisitorA();       Element* elm = new ConcreteElementA();       elm->Accept(vis);        } | 
2.3.2  代码说明
Visitor模式的实现过程中有以下的地方要注意:
1)Visitor类中的Visit()操作的实现。
u 这里我们可以向Element类仅仅提供一个接口Visit(),而在Accept()实现中具体调用哪一个Visit()操作则通过函数重载(overload)的方式实现:我们提供Visit()的两个重载版本a)Visit(ConcreteElementA* elmA),b)Visit(ConcreteElementB* elmB)。
u       在C++中我们还可以通过RTTI(运行时类型识别:Runtime type identification)来实现,即我们只提供一个Visit()函数体,传入的参数为Element*型别参数 ,然后用RTTI决定具体是哪一类的ConcreteElement参数,再决定具体要对哪个具体类施加什么样的具体操作。【注释2】RTTI给接口带来了简单一致性,但是付出的代价是时间(RTTI的实现)和代码的Hard编码(要进行强制转换)。
2.4 讨论
有时候我们需要为Element提供更多的修改,这样我们就可以通过为Element提供一系列的
Visitor模式可以使得Element在不修改自己的同时增加新的操作,但是这也带来了至少以下的两个显著问题:
1) 破坏了封装性。Visitor模式要求Visitor可以从外部修改Element对象的状态,这一般通过两个方式来实现:a)Element提供足够的public接口,使得Visitor可以通过调用这些接口达到修改Element状态的目的;b)Element暴露更多的细节给Visitor,或者让Element提供public的实现给Visitor(当然也给了系统中其他的对象),或者将Visitor声明为Element的friend类,仅将细节暴露给Visitor。但是无论那种情况,特别是后者都将是破坏了封装性原则(实际上就是C++的friend机制得到了很多的面向对象专家的诟病)。
2) ConcreteElement的扩展很困难:每增加一个Element的子类,就要修改Visitor的
接口,使得可以提供给这个新增加的子类的访问机制。从上面我们可以看到,或者增加一个用于处理新增类的Visit()接口,或者重载一个处理新增类的Visit()操作,或者要修改RTTI方式实现的Visit()实现。无论那种方式都给扩展新的Element子类带来了困难。
【注释2】:我们可以通过RTTI来实现Visit()接口的单一,示例代码省略。
 
                    
                 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号