问题
在面向对象系统的开发和设计过程中,经常会遇到一种情况就是需求变更,经常我们做好的一个设计,实现了一个系统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见的就是解决方案就是给已经设计实现好的类添加新的方法去实现客户的需求,这样就陷入了设计变更的梦魔:不停的打补丁,其带来的后果就是设计根本就不可能封闭,编译永远都是整个系统代码。
visitor模式则提供了一种解决方案:将更新封装到一个类中(访问操作),并由待更改类提供一个接受接口,则可达到效果。
visitor访问者模式
表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
适用于数据结构稳定的系统。它把数据结构和作用于数据结构上的操作分离开,使得操作集合。
优点:新增加操作很容易,因为增加新操作就相当于增加一个访问者,访问者模式将有关的行为集中到一个访问者对象中。
解析:visitor模式把对结点的访问封装成一个抽象基类,通过派生出不同的子类生产新的访问方式。在实现的时候,在visitor抽象基类中声明了对所有不同结点进行访问的接口函数。这样也造成了visitor模式的一个缺陷:新加入一个结点的时候都要添加visitor中的对其进行访问接口函数,这样使得所有的visitor及其派生类都要重新编译了,也就是说visitor模式一个缺点就是添加新的结点十分困难。另外,还需要指出的是visitor模式采用了所谓的”双分派”技术。
小demo
visitor.h
#ifndef VISITOR_H
#define VISITOR_H
class Visitor;
class Element
{
public:
virtual ~Element(){}
virtual void Accept(Visitor &rVisitor) = 0;
protected:
Element(){}
};
class ConcreteElementA : public Element
{
public:
virtual ~ConcreteElementA() {}
virtual void Accept(Visitor &rVisitor);
};
class ConcreteElementB : public Element
{
public:
virtual ~ConcreteElementB() {}
virtual void Accept(Visitor &rVisitor);
};
class Visitor
{
public:
virtual ~Visitor(){}
virtual void VisitConcreteElementA(Element *elm) = 0;
virtual void VisitConcreteElementB(Element *elm) = 0;
protected:
Visitor(){}
};
class ConcreteVisitorA : public Visitor
{
public:
virtual ~ConcreteVisitorA(){}
virtual void VisitConcreteElementA(Element *elm);
virtual void VisitConcreteElementB(Element *elm);
};
class ConcreteVisitorB : public Visitor
{
public:
virtual ~ConcreteVisitorB(){}
virtual void VisitConcreteElementA(Element *elm);
virtual void VisitConcreteElementB(Element *elm);
};
#endif
visitor.cpp
#include "Visitor.h"
#include <iostream>
void ConcreteElementA::Accept(Visitor &rVisitor)
{
rVisitor.VisitConcreteElementA(this);
std::cout<<"visiting ConcreteElementA\n";
}
void ConcreteElementB::Accept(Visitor &rVisitor)
{
rVisitor.VisitConcreteElementB(this);
std::cout<<"visiting ConcreteElementB\n";
}
void ConcreteVisitorA::VisitConcreteElementA(Element *elm)
{
std::cout << "VisitConcreteElementA By ConcreteVisitorA\n";
}
void ConcreteVisitorA::VisitConcreteElementB(Element *elm)
{
std::cout << "VisitConcreteElementB By ConcreteVisitorA\n";
}
void ConcreteVisitorB::VisitConcreteElementA(Element *elm)
{
std::cout << "VisitConcreteElementA By ConcreteVisitorB\n";
}
void ConcreteVisitorB::VisitConcreteElementB(Element *elm)
{
std::cout << "VisitConcreteElementB By ConcreteVisitorB\n";
}
main.cpp
#include "Visitor.h"
#include <windows.h>
int main()
{
Visitor *pVisitorA = new ConcreteVisitorA();
Element *pElement = new ConcreteElementA();
pElement->Accept(*pVisitorA);
delete pElement;
delete pVisitorA;
system("pause");
return 0;
}
visitor模式在不破坏类的前提下,为类提供新的操作。visitor模式的关键是”双分派技术”。C++语言支持的是单分派。
双分派技术:双分派意味着执行的操作将取决于请求的种类和接收者的类型。
在visitor模式中Accept()操作是一个双分派的操作。具体调用哪一个具体的Accept()操作,有两个决定因素:1.Element类型。因为Accept()是多态的操作,需要具体的Element类型的子类才可以决定到底调用哪一个Accept()实现;2.Visitor的类型。Accept()操作有一个参数(Visitor&vis),要决定实际传进来的visitor的实际类别才可以决定具体调用哪一个visitConcrete()实现。
有时候我们需要为Element提供更多的修改,这样我们就可以通过为Element提供一系列的Visitor模式可以使得Element在不修改自己的同时增加新的操作,但是这带来了一个显著问题:ConcreteElement的扩展很困难:每增加一个Element的子类,就要修改Visitor的接口,使得可以提供给这个新增加的子类的访问机制。