目录
1.概述
在访问者模式(Visitor Pattern)中,我们使用了一个访问者类,它改变了元素类的执行算法。通过这种方式,元素的执行算法可以随着访问者改变而改变。这种类型的设计模式属于行为型模式。根据模式,元素对象已接受访问者对象,这样访问者对象就可以处理元素对象上的操作。
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
2.结构
访问者模式(Visitor Pattern)是一种将数据操作与数据结构分离的设计模式。这种设计模式允许你在不改变数据结构的前提下定义新的操作。在访问者模式中,通常有两个主要的组件:元素(Element)和访问者(Visitor)。
UML结构图如下所示:
角色定义:
抽象访问者(Visitor)角色:声明了一个或者多个访问操作,形成所有的具体元素角色必须实现的接口,用来代表对象结构添加的功能。
具体访问者(ConcreteVisitor)角色:实现抽象访问者角色所声明的接口,也就是抽象访问者所声明的各个访问操作。
抽象节点(Element)角色:抽象的元素对象,对象结构的顶层接口,定义接收访问的操作。
具体节点(ConcreteElement)角色:具体元素对象,对象结构中具体的对象,也是被访问的对象,通常回调访问者的真实功能,同时开放自身的数据提供访问者使用。
结构对象(ObjectStructure)角色:有如下的一些责任,可以遍历结构中的所有元素;如果需要,提供一个高层次的接口让访问者对象可以访问每一个元素;如果需要,可以设计成一个复合对象或者一个聚集,如列(List)或集合(Set)。
调用顺序:
3.简单实现
下面是一个简单的C++访问者模式的示例:
#include <iostream>
#include <string>
#include <functional>
#include <map>
#include <vector>
using namespace std;
class Element;
class CPU;
class VideoCard;
class MainBoard;
class Visitor {
public:
Visitor(const std::string& name) : m_visitorName(name) { }
virtual void visit( CPU* cpu ) = 0 ;
virtual void visit( VideoCard* videoCard ) = 0;
virtual void visit( MainBoard* mainBoard ) = 0;
std::string getName() const {
return this->m_visitorName;
};
private:
std::string m_visitorName;
};
class Element {
public:
Element( const std::string& name ) : m_eleName(name) { }
virtual void accept( Visitor* visitor ) = 0;
virtual std::string getName() const {
return this->m_eleName;
}
private:
std::string m_eleName;
};
class CPU : public Element {
public:
CPU(const std::string& name) : Element(name) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
class VideoCard : public Element {
public:
VideoCard(const std::string& name) : Element(name) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
class MainBoard : public Element {
public:
MainBoard(const std::string& name) : Element(name) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
class CircuitDetector : public Visitor {
public:
CircuitDetector(const std::string& name) : Visitor(name) {}
// checking cpu
void visit( CPU* cpu ) override {
std::cout << Visitor::getName() << " is checking CPU's circuits.(" << cpu->getName()<<")" << std::endl;
}
// checking videoCard
void visit( VideoCard* videoCard ) override {
std::cout << Visitor::getName() << " is checking VideoCard's circuits.(" << videoCard->getName()<<")" << std::endl;
}
// checking mainboard
void visit( MainBoard* mainboard ) override {
std::cout << Visitor::getName() << " is checking MainBoard's circuits.(" << mainboard->getName() <<")" << std::endl;
}
};
class FunctionDetector : public Visitor {
public:
FunctionDetector(const std::string& name) : Visitor(name) {}
void visit( CPU* cpu ) override {
std::cout << Visitor::getName() << " is check CPU's function.(" << cpu->getName() << ")"<< std::endl;
}
// checking videoCard
void visit( VideoCard* videoCard ) override {
std::cout << Visitor::getName() << " is checking VideoCard's function.(" << videoCard->getName()<< ")" << std::endl;
}
// checking mainboard
void visit( MainBoard* mainboard ) override {
std::cout << Visitor::getName() << " is checking MainBoard's function.(" << mainboard->getName() << ")"<< std::endl;
}
};
class Computer {
public:
Computer(CPU* cpu,
VideoCard* videocard,
MainBoard* mainboard) {
m_elementList.push_back(cpu);
m_elementList.push_back(videocard);
m_elementList.push_back(mainboard);
};
void Accept(Visitor* visitor) {
for( auto& it : m_elementList)
{
it->accept(visitor);
}
};
private:
std::vector<Element*> m_elementList;
};
int main(){
std::unique_ptr<CPU> cpu(new CPU("Intel CPU"));
std::unique_ptr<VideoCard> videocard(new VideoCard("XXX video card"));
std::unique_ptr<MainBoard> mainboard(new MainBoard("HUAWEI mainboard"));
std::unique_ptr<Computer> myComputer(new Computer(cpu.get(), videocard.get(), mainboard.get()));
std::unique_ptr<CircuitDetector> Dan(new CircuitDetector("CircuitDetector Dan"));
std::unique_ptr<FunctionDetector> Tom(new FunctionDetector("FunctionDetector Tom"));
std::cout << "\nStep 1: Dan is checking computer's circuits." << std::endl;
myComputer->Accept(Dan.get());
std::cout << "\nStep 2: Tom is checking computer's functions." << std::endl;
myComputer->Accept(Tom.get());
return 0;
}
在这个示例中,Visitor
接口定义了访问CPU、VideoCard和MainBoard的方法。每个具体的元素类(CPU、VideoCard和MainBoard)都实现了accept
方法,该方法接受一个Visitor
对象并调用其相应的visit
方法。Computer 就相当于ObjectStructure,实现了对传入visitor对所有元素的访问。在客户端代码中,我们创建了一个CircuitDetector、FunctionDetector
对象和三个具体的元素对象,并使用accept
方法将访问者应用于这些元素。
4.基于可变参数模板实现
GOF《设计模式》一书中也明确指出了Visitor模式需要注意的问题:定义对象结构的类很少改变,但经常需要在此结构上定义新的操作。改变对象结构类需要重定义对所有访问者的接口,这可能需要付出很大的代价。如果对象结构类经常改变,那么还是在这些类中定义这些操作较好。
也就是说,在访问者模式中被访问者应该是一个稳定的继承体系,如果这个继承体系经常变化,就会导致经常修改Visitor基类,因为在Visitor基类中定义了需要访问的对象类型,每增加一种被访问类型就要增加一个对应的纯虚函数,在上例中,如果需要增加一个新的被访问者ConcreteElement3,则需要在Visitor基类中增加一个纯虚函数:
virtual void Visit(ConcreteElement3* element) = 0;
根据面向接口编程的原则,我们应该依赖于接口而不应依赖于实现,因为接口是稳定的,不会变化的。而访问者模式的接口不太稳定,这会导致整个系统的不稳定,存在很大的隐患。要解决这个问题,最根本的方法是定义一个稳定的Visitor接口层,即不会因为增加新的被访问者而修改接口层,能否定义一个稳定的Visitor接口层呢?答案是肯定的,通过C++11进行改进,我们就可以实现这个目标。
通过可变参数模板就可以实现一个稳定的接口层,利用可变参数模板可以支持任意个数的参数的特点,可以让访问者接口层访问任意个数的被访问者,这样就不需要每增加一个新的被访问者就修改接口层,从而使接口层保持稳定。使用C++11改进后的visitor模式代码如下:
template <typename... Types>
class VisitorT;
template <typename T, typename... Types>
class VisitorT<T,Types...> : VisitorT<Types...>{
public:
using VisitorT<Types...>::Visit;//避免覆盖父类的同名方法
virtual void Visit(T&) = 0;
};
template <typename T>
class VisitorT<T>{
public:
virtual void Visit(T&) = 0;
};
上述代码为每个类型定义了一个纯虚函数Visit。通过"using Visitor<Types...>::Visit;"可以避免影藏基类的同名方法。
被访问的继承体系使用Visitor访问该继承体系的对象,第3章节的例子代码可以修改为:
class Element;
class CPU;
class VideoCard;
class MainBoard;
class Element;
using VisitorBase = VisitorT<CPU,VideoCard,MainBoard> ;
/*------------------*/
class Visitor : public VisitorBase {
public:
Visitor(const std::string& name) : m_visitorName(name) { }
std::string getName() const {
return this->m_visitorName;
};
private:
std::string m_visitorName;
};
class Element {
public:
Element(const std::string& name ) : m_eleName(name){ }
virtual void accept( Visitor& visitor ) {};
virtual std::string getName() {
return this->eleName;
}
private:
std::string m_eleName;
};
/*----------- Elements -------------*/
class CPU : public Element {
public:
CPU(const std::string& name) : Element(std::move(name)) {}
void accept(Visitor& visitor) override {
visitor.Visit(*this);
}
};
class VideoCard : public Element {
public:
VideoCard(const std::string& name) : Element(std::move(name)) {}
void accept(Visitor& visitor) override {
visitor.Visit(*this);
}
};
class MainBoard : public Element {
public:
MainBoard(const std::string& name) : Element(std::move(name)) {}
void accept(Visitor& visitor) override {
visitor.Visit(*this);
}
};
/*----------- ConcreteVisitor -------------*/
class CircuitDetector : public Visitor{
public:
CircuitDetector(const std::string& name) : Visitor(name) {}
// checking cpu
void Visit(CPU& cpu ) override {
std::cout << Visitor::getName() << " is checking CPU's circuits.(" << cpu.getName()<<")" << std::endl;
}
// checking videoCard
void Visit( VideoCard& videoCard ) override {
std::cout << Visitor::getName() << " is checking VideoCard's circuits.(" << videoCard.getName()<<")" << std::endl;
}
// checking mainboard
void Visit( MainBoard& mainboard ) override {
std::cout << Visitor::getName() << " is checking MainBoard's circuits.(" << mainboard.getName() <<")" << std::endl;
}
};
class FunctionDetector : public Visitor {
public:
FunctionDetector(const std::string& name) : Visitor(name) {}
void Visit( CPU& cpu ) override {
std::cout << Visitor::getName() << " is check CPU's function.(" << cpu.getName() << ")"<< std::endl;
}
// checking videoCard
void Visit( VideoCard& videoCard ) override {
std::cout << Visitor::getName() << " is checking VideoCard's function.(" << videoCard.getName()<< ")" << std::endl;
}
// checking mainboard
void Visit( MainBoard& mainboard ) override {
std::cout << Visitor::getName() << " is checking MainBoard's function.(" << mainboard.getName() << ")"<< std::endl;
}
};
/*------------------------*/
class Computer {
public:
Computer(CPU* cpu,
VideoCard* videocard,
MainBoard* mainboard) {
m_elementList.push_back(cpu);
m_elementList.push_back(videocard);
m_elementList.push_back(mainboard);
};
void Accept(Visitor& visitor) {
for(auto& it : m_elementList)
{
it->accept(visitor);
}
};
private:
std::vector<Element*> m_elementList;
};
int main(){
std::unique_ptr<CPU> cpu(new CPU("Intel CPU"));
std::unique_ptr<VideoCard> videocard(new VideoCard("XXX video card"));
std::unique_ptr<MainBoard> mainboard(new MainBoard("HUAWEI mainboard"));
std::unique_ptr<Computer> myComputer(new Computer(cpu.get(), videocard.get(), mainboard.get()));
std::unique_ptr<CircuitDetector> Dan(new CircuitDetector("CircuitDetector Dan"));
std::unique_ptr<FunctionDetector> Tom(new FunctionDetector("FunctionDetector Tom"));
std::cout << "\nStep 1: Dan is checking computer's circuits." << std::endl;
myComputer->Accept(Dan.get());
std::cout << "\nStep 2: Tom is checking computer's functions." << std::endl;
myComputer->Accept(Tom.get());
return 0;
}
测试同上面的一样效果。
5.使用场景
1)对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2)需要对一个对象结构中的对象进行很多不同的并且不相关的操作,而需要避免让这些操作"污染"这些对象的类,也不希望在增加新操作时修改这些类。
3)访问者可以对功能进行统一,可以做报表、UI、拦截器与过滤器。
6.总结
优点:
-
扩展性:访问者模式使得在不修改已有类的情况下,可以轻松地增加新的操作。这符合开放-封闭原则(Open-Closed Principle),即对扩展开放,对修改封闭。
-
分离操作与数据结构:访问者模式将操作从数据结构中分离出来,使得数据结构可以独立地变化,而无需修改相关的操作。这有助于减少代码的耦合度,提高系统的可维护性。
-
灵活性:访问者模式允许操作以不同的方式实现,而不影响数据结构。这提供了更大的灵活性,可以根据需要选择不同的操作实现。
-
双重分派:访问者模式实现了运行时动态地决定调用哪个操作,这种机制称为双重分派,增加了系统的动态性。
-
跨类层次结构的操作:当需要在多个类层次结构中执行相同的操作时,访问者模式可以避免在每个类层次结构中重复实现相同的操作。
缺点:
-
增加类的数量:对于每个需要访问的数据类,都需要有一个对应的访问者类,这可能导致类的数量显著增加,增加系统的复杂性。
-
修改困难:如果数据结构发生变化(例如添加新的子类),可能需要修改所有的访问者类来适应这些变化。这可能导致维护成本的增加。
-
破坏封装性:访问者模式需要被访问的类暴露出其内部状态给访问者对象,这可能会破坏类的封装性。
-
性能开销:由于访问者模式涉及到多重分派和类型检查,可能会导致一定的性能开销。
-
可能过度使用:在某些情况下,过度使用访问者模式可能会导致代码变得复杂且难以理解。如果操作与数据结构紧密相关,或者操作数量有限,则可能不需要使用访问者模式。
访问者模式在某些场景下非常有用,特别是当需要在不修改类层次结构的情况下添加新的操作时。然而,它也带来了一些缺点,如增加类的数量、修改困难等。因此,在决定是否使用访问者模式时,需要仔细权衡其优缺点,并考虑具体的应用场景和需求。