一、问题引出
在面向对象系统的开发和设计过程中,经常会遇到需求变更(Requirement Changing)的情况。通常,我们已经完成了一个设计并实现了系统原型,但客户又提出了新的需求。为了满足这些新需求,我们不得不修改已有的设计。最常见的解决方案是给已经设计好的类添加新的方法,但这会导致设计不断被打补丁,系统代码频繁重新编译,最终使得设计难以封闭,代码难以维护。
Visitor 模式提供了一种优雅的解决方案:将更新(变更)封装到一个类中(访问操作),并由待更改类提供一个接收接口,从而实现需求变更的灵活扩展。
二、模式选择
我们通过 Visitor 模式解决上述问题。Visitor 模式的典型结构图如下:

Visitor 模式的核心思想是 **双分派(Double-Dispatch)技术**。C++ 语言本身支持的是单分派,但通过 Visitor 模式,我们可以实现双分派的效果。
在 Visitor 模式中,`Accept()` 操作是一个双分派的操作。具体调用哪一个 `Accept()` 实现,取决于两个因素:
(1)Element 的类型:因为 `Accept()` 是多态操作,需要具体的 `Element` 子类来决定调用哪个 `Accept()` 实现。
(2)Visitor 的类型:`Accept()` 操作有一个参数(`Visitor* vis`),需要根据实际传入的 `Visitor` 类型来决定调用哪个 `VisitConcrete()` 实现。
三、代码实现
下面我们将通过一个完整的 C++ 代码示例来展示如何实现 Visitor 模式。
代码片段 1:Visitor.h
cpp
// Visitor.h
#ifndef _VISITOR_H_
#define _VISITOR_H_
class ConcreteElementA;
class ConcreteElementB;
class Visitor {
public:
virtual ~Visitor() {}
virtual void VisitConcreteElementA(ConcreteElementA* elmA) = 0;
virtual void VisitConcreteElementB(ConcreteElementB* elmB) = 0;
protected:
Visitor() {}
};
class ConcreteVisitorA : public Visitor {
public:
ConcreteVisitorA() {}
virtual ~ConcreteVisitorA() {}
virtual void VisitConcreteElementA(ConcreteElementA* elmA);
virtual void VisitConcreteElementB(ConcreteElementB* elmB);
};
class ConcreteVisitorB : public Visitor {
public:
ConcreteVisitorB() {}
virtual ~ConcreteVisitorB() {}
virtual void VisitConcreteElementA(ConcreteElementA* elmA);
virtual void VisitConcreteElementB(ConcreteElementB* elmB);
};
#endif //~_VISITOR_H_
代码片段 2:Visitor.cpp
cpp
// Visitor.cpp
#include "Visitor.h"
#include "Element.h"
#include <iostream>
using namespace std;
Visitor::Visitor() {
// 构造函数
}
Visitor::~Visitor() {
// 析构函数
}
void ConcreteVisitorA::VisitConcreteElementA(ConcreteElementA* elmA) {
cout << "ConcreteVisitorA is visiting ConcreteElementA..." << endl;
}
void ConcreteVisitorA::VisitConcreteElementB(ConcreteElementB* elmB) {
cout << "ConcreteVisitorA is visiting ConcreteElementB..." << endl;
}
void ConcreteVisitorB::VisitConcreteElementA(ConcreteElementA* elmA) {
cout << "ConcreteVisitorB is visiting ConcreteElementA..." << endl;
}
void ConcreteVisitorB::VisitConcreteElementB(ConcreteElementB* elmB) {
cout << "ConcreteVisitorB is visiting ConcreteElementB..." << endl;
}
代码片段 3:Element.h
cpp
// Element.h
#ifndef _ELEMENT_H_
#define _ELEMENT_H_
class Visitor;
class Element {
public:
virtual ~Element() {}
virtual void Accept(Visitor* vis) = 0;
protected:
Element() {}
};
class ConcreteElementA : public Element {
public:
ConcreteElementA() {}
virtual ~ConcreteElementA() {}
virtual void Accept(Visitor* vis);
};
class ConcreteElementB : public Element {
public:
ConcreteElementB() {}
virtual ~ConcreteElementB() {}
virtual void Accept(Visitor* vis);
};
#endif //~_ELEMENT_H_
代码片段 4:Element.cpp
cpp
// Element.cpp
#include "Element.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
Element::Element() {
// 构造函数
}
Element::~Element() {
// 析构函数
}
void ConcreteElementA::Accept(Visitor* vis) {
vis->VisitConcreteElementA(this);
cout << "ConcreteElementA is being visited..." << endl;
}
void ConcreteElementB::Accept(Visitor* vis) {
vis->VisitConcreteElementB(this);
cout << "ConcreteElementB is being visited..." << endl;
}
代码片段 5:main.cpp
cpp
// main.cpp
#include "Element.h"
#include "Visitor.h"
#include <iostream>
using namespace std;
int main(int argc, char* argv[]) {
Visitor* visA = new ConcreteVisitorA();
Visitor* visB = new ConcreteVisitorB();
Element* elmA = new ConcreteElementA();
Element* elmB = new ConcreteElementB();
elmA->Accept(visA); // ConcreteVisitorA 访问 ConcreteElementA
elmB->Accept(visA); // ConcreteVisitorA 访问 ConcreteElementB
elmA->Accept(visB); // ConcreteVisitorB 访问 ConcreteElementA
elmB->Accept(visB); // ConcreteVisitorB 访问 ConcreteElementB
delete visA;
delete visB;
delete elmA;
delete elmB;
return 0;
}
代码说明
在实现 Visitor 模式时,需要注意以下几点:
(1)Visitor 类中的 Visit() 操作:
可以为 Element 类提供一个统一的 Visit() 接口,在 Accept() 中通过函数重载(overload)的方式调用具体的 Visit() 实现。
在 C++ 中,还可以通过 RTTI(运行时类型识别)来实现,即提供一个统一的 Visit() 函数体,传入 Element* 类型的参数,然后在函数内部通过 RTTI 决定具体的 ConcreteElement 类型,并执行相应的操作。
(2)双分派机制:
双分派机制使得 Accept() 操作能够根据 Element 和 Visitor 的类型动态决定调用哪个具体的 Visit() 实现。
四、总结讨论
Visitor 模式可以在不修改 `Element` 类的情况下为其增加新的操作,但这也带来了一些问题:
(1)破坏了封装性:
Visitor 模式要求 Visitor 可以从外部修改 Element 对象的状态,这通常通过以下两种方式实现:
Element 提供足够的 public 接口,使得 Visitor 可以通过调用这些接口修改 Element 的状态。
Element 暴露更多的细节给 Visitor,或者将 Visitor 声明为 Element 的 friend 类。这种方式会破坏封装性原则。
(2)扩展困难:
每增加一个 Element 的子类,就需要修改 Visitor 的接口,以提供对新子类的访问机制。无论是增加新的 Visit() 接口,还是修改 RTTI 实现,都会增加扩展的复杂性。
Visitor 模式是一种强大的设计模式,特别适用于需要在不修改现有类的情况下为其增加新操作的场景。然而,使用 Visitor 模式时需要权衡其带来的灵活性和对封装性的破坏,以及扩展时的复杂性。在实际开发中,应根据具体需求谨慎选择是否使用 Visitor 模式。