前言

尊重原创,本系列文本解析部分主要基于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模式解决上面的问题,其典型的结构图为:
Visitor Pattern
图 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()的两个重载版本
      • VisitConcreteElementA* elmA);
      • VisitConcreteElementB* 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声明为Elementfriend类,仅将细节暴露给Visitor。但是无论那种情况,特别是后者都将是破坏了封装性原则(实际上就是C++的friend机制得到了很多的面向对象专家的诟病)
  • ConcreteElement的扩展很困难:每增加一个Element的子类,就要修改Visitor的接口,使得可以提供给这个新增加的子类的访问机制。从上面我们可以看到,或者增加一个用于处理新增类的Visit()接口,或者重载一个处理新增类的Visit()操作,或者要修改RTTI方式实现的Visit()实现。无论那种方式都给扩展新的Element子类带来了困难。