前言
尊重原创,本系列文本解析部分主要基于Candidate for Master’s Degree School of Computer Wuhan University的K_Eckel([email protected] )《设计模式精解-GoF 23 种设计模式解析附 C++实现源码》。为了避免重造轮子,本系列博文对源码在ubuntu16.04下做了验证并上传到了gitee ,再次感谢。
如有问题,可邮件联系我([email protected] )并共同探讨解决方案。
目录
创建型模式(Creating Pattern)
Factory 模式 | AbstactFactory 模式 | Singleton 模式 | Builder 模式 | Prototype 模式
结构型模式(Structrual Pattern)
Bridge 模式 | Adapter 模式 | Decorator 模式 | Composite 模式 | Flyweight 模式 | Facade 模式 | Proxy 模式
行为型模式(Behavioral Pattern)
Template 模式 | Strategy 模式 | State 模式 | Observer 模式 | Memento 模式 | Mediator 模式 | Command 模式 | Visitor 模式 | Iterator 模式 | Interpreter 模式 | Chain of Responsibility 模式
Visitor 模式简介:
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
比如您在朋友家做客,您是访问者,朋友接受您的访问,您通过朋友的描述,然后对朋友的描述做出一个判断,这就是访问者模式。
问题
在面向对象系统的开发和设计过程,经常会遇到一种情况就是需求变更(RequirementChanging),经常我们做好的一个设计、实现了一个系统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见就是解决方案就是给已经设计、实现好的类添加新的方法去实现客户新的需求,这样就陷入了设计变更的梦魇:不停地打补丁,其带来的后果就是设计根本就不可能封闭、编译永远都是整个系统代码。
Visitor模式则提供了一种解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,则可达到效果。
模式选择
我们通过Visitor模式解决上面的问题,其典型的结构图为:
图 1:Observer 模式结构示意图
Visitor模式在不破坏类的前提下,为类提供增加新的新操作。Visitor模式的关键是双分派(Double-Dispatch)的技术【注释1】 。C++语言支持的是单分派。
【注释1】:双分派意味着执行的操作将取决于请求的种类和接收者的类型。更多资料请参考资料。
在Visitor模式中Accept()
操作是一个双分派的操作。具体调用哪一个具体的Accept()
操作,有两个决定因素:
Element
的类型。因为Accept()
是多态的操作,需要具体的Element
类型的子类才可以决定到底调用哪一个Accept()
实现;
Visitor
的类型。Accept()
操作有一个参数(Visitor* vis
),要决定了实际传进来的Visitor
的实际类别才可以决定具体是调用哪个VisitConcrete()
实现。
实现
完整代码示例(code)
Visitor模式的实现很简单,这里为了方便初学者的学习和参考,将给出完整的实现代码(所有代码采用 C++实现,并在 Visual Studio Code,Version: 1.36.1 下测试运行)。
源码gitee地址:点击这里
代码目录结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Visitor> tree /F 卷 Windows 的文件夹 PATH 列表 卷序列号为 F0C5-AFA6 C:. ├─include │ Element.h │ Visitor.h │ └─src a.out Element.cpp main.cpp Visitor.cpp PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Visitor>
Element.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // Element.h #ifndef _ELEMENT_H_ #define _ELEMENT_H_ class Visitor; class Element { private: /* data */ protected: Element(/* args */); public: virtual ~Element(); virtual void Accept(Visitor* vis) = 0; }; class ConcreteElementA:public Element { private: /* data */ public: ConcreteElementA(/* args */); ~ConcreteElementA(); void Accept(Visitor* vis); }; class ConcreteElementB:public Element { private: /* data */ public: ConcreteElementB(/* args */); ~ConcreteElementB(); void Accept(Visitor* vis); }; #endif //~_ELEMENT_H_
Visitor.h:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 // Visitor.h #ifndef _VISITOR_H_ #define _VISITOR_H_ class ConcreteElementA; class ConcreteElementB; class Element; class Visitor { private: /* data */ protected: Visitor(/* args */); public: virtual ~Visitor(); virtual void VisitConcreteElementA(Element* elm) = 0; virtual void VisitConcreteElementB(Element* elm) = 0; }; class ConcreteVisitorA : public Visitor { private: /* data */ public: ConcreteVisitorA(/* args */); virtual ~ConcreteVisitorA(); virtual void VisitConcreteElementA(Element* elm); virtual void VisitConcreteElementB(Element* elm); }; class ConcreteVisitorB : public Visitor { private: /* data */ public: ConcreteVisitorB(/* args */); ~ConcreteVisitorB(); virtual void VisitConcreteElementA(Element* elm); virtual void VisitConcreteElementB(Element* elm); }; #endif //~_VISITOR_H_
Element.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // Element.cpp #include "../include/Element.h" #include <iostream> #include "../include/Visitor.h" using namespace std; Element::Element(/* args */) {} Element::~Element() {} void Element::Accept(Visitor* vis) {} ConcreteElementA::ConcreteElementA(/* args */) {} ConcreteElementA::~ConcreteElementA() {} void ConcreteElementA::Accept(Visitor* vis) { vis->VisitConcreteElementA(this); printf("visiting ConcreteElementA... \n"); } ConcreteElementB::ConcreteElementB(/* args */) {} ConcreteElementB::~ConcreteElementB() {} void ConcreteElementB::Accept(Visitor* vis) { vis->VisitConcreteElementA(this); printf("visiting ConcreteElementA... \n"); }
Visitor.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 // Visitor.cpp #include "../include/Visitor.h" #include <iostream> #include "../include/Element.h" using namespace std; Visitor::Visitor(/* args */) {} Visitor::~Visitor() {} ConcreteVisitorA::ConcreteVisitorA(/* args */) {} ConcreteVisitorA::~ConcreteVisitorA() {} void ConcreteVisitorA::VisitConcreteElementA(Element* elm) { printf("I will visit ConcreteElementA... \n"); } void ConcreteVisitorA::VisitConcreteElementB(Element* elm) { printf("I will visit ConcreteElementB... \n"); } ConcreteVisitorB::ConcreteVisitorB(/* args */) {} ConcreteVisitorB::~ConcreteVisitorB() {} void ConcreteVisitorB::VisitConcreteElementA(Element* elm) { printf("I will visit ConcreteElementA... \n"); } void ConcreteVisitorB::VisitConcreteElementB(Element* elm) { printf("I will visit ConcreteElementB... \n"); }
main.cpp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // main.cpp #include <iostream> #include "../include/Element.h" #include "../include/Visitor.h" using namespace std; int main(int argc, char* argv[]) { Visitor* vis = new ConcreteVisitorA(); Element* elm = new ConcreteElementA(); elm->Accept(vis); return 0; }
代码说明
Visitor模式的实现过程中有以下的地方要注意:
Visitor
类中的Visit()
操作的实现。
这里我们可以向Element
类仅仅提供一个接口Visit()
,而在Accept()
实现中具体调用哪一个Visit()
操作则通过函数重载(overload)的方式实现:我们提供Visit()
的两个重载版本
Visit
(ConcreteElementA* elmA
);
Visit
(ConcreteElementB* elmB
)。
在C++中我们还可以通过RTTI(运行时类型识别:Runtime type identification)来实现,即我们只提供一个Visit()
函数体,传入的参数为Element*
型别参数,然后用RTTI
决定具体是哪一类的ConcreteElement
参数,再决定具体要对哪个具体类施加什么样的具体操作。RTTI给接口带来了简单一致性,但是付出的代价是时间(RTTI的实现)和代码的Hard编码(要进行强制转换)。
编译运行结果:
1 2 3 4 5 PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Visitor\src> g++ *.cpp -std=c++11 PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Visitor\src> .\a.exe I will visit ConcreteElementA... visiting ConcreteElementA... PS C:\Users\guopz\Desktop\GiteeBlog\designpatternsbycpipei\behavioralPattern\Visitor\src>
讨论
有时候我们需要为Element提供更多的修改,这样我们就可以通过为Element提供一系列的 Visitor模式可以使得Element在不修改自己的同时增加新的操作,但是这也带来了至少以下的两个显著问题:
破坏了封装性。Visitor模式要求Visitor
可以从外部修改Element
对象的状态,这一般通过两个方式来实现:
Element
提供足够的public
接口,使得Visitor
可以通过调用这些接口达到修改Element
状态的目的;
Element
暴露更多的细节给Visitor
,或者让Element
提供public
的实现给Visitor
(当然也给了系统中其他的对象),或 者将Visitor
声明为Element
的friend
类,仅将细节暴露给Visitor
。但是无论那种情况,特别是后者都将是破坏了封装性原则(实际上就是C++的friend
机制得到了很多的面向对象专家的诟病)
ConcreteElement
的扩展很困难:每增加一个Element
的子类,就要修改Visitor
的接口,使得可以提供给这个新增加的子类的访问机制。从上面我们可以看到,或者增加一个用于处理新增类的Visit()
接口,或者重载一个处理新增类的Visit()
操作,或者要修改RTTI方式实现的Visit()
实现。无论那种方式都给扩展新的Element
子类带来了困难。