[设计模式]Visitor访问者模式

问题

在面向对象系统的开发和设计过程中,经常会遇到一种情况就是需求变更,经常我们做好的一个设计,实现了一个系统原型,咱们的客户又会有了新的需求。我们又因此不得不去修改已有的设计,最常见的就是解决方案就是给已经设计实现好的类添加新的方法去实现客户的需求,这样就陷入了设计变更的梦魔:不停的打补丁,其带来的后果就是设计根本就不可能封闭,编译永远都是整个系统代码。

visitor模式则提供了一种解决方案:将更新封装到一个类中(访问操作),并由待更改类提供一个接受接口,则可达到效果。

visitor访问者模式

将作用于某种数据结构中的各元素的操作分离出来封装成独立的类(就是访问者类)/用了一个访问者类,使其在不改变数据结构的前提下可以添加作用于这些元素的新的操作,为数据结构中的每个元素提供多种访问方式。它将对数据的操作与数据结构进行分离。

意图:主要将数据结构与数据操作分离
主要解决:稳定的数据结构和易变的操作耦合问题。
如何解决:在被访问的类里面加一个对外提供接待访问者的接口
关键代码:在数据基础类里面有一个方法接受访问者,将自身引用传入访问者。
优点

  • 扩展性好。能够在不修改对象结构中的元素的情况下,为对象结构中的元素添加新的功能。
  • 复用性好。可以通过访问者来定义整个对象结构通用的功能,从而提高系统的复用程度。
  • 灵活性好。访问者模式将数据结构与作用于结构上的操作解耦,使得操作集合可相对自由地演化而不影响系统的数据结构。
  • 符合单一职责原则。访问者模式把相关的行为封装在一起,构成一个访问者,使每一个访问者的功能都比较单一。

缺点

  • 增加新的元素类很困难。在访问者模式中,每增加一个新的元素类,都要在每一个具体访问者类中增加相应的具体操作,这违背了“开闭原则”。
  • 破坏封装。访问者模式中具体元素对访问者公布细节,这破坏了对象的封装性。
  • 违反了依赖倒置原则。访问者模式依赖了具体类,而没有依赖抽象类。

[设计模式]Visitor访问者模式_第1张图片

visitor模式把对结点的访问封装成一个抽象基类,通过派生出不同的子类生产新的访问方式。在实现的时候,在visitor抽象基类中声明了对所有不同结点进行访问的接口函数。这样也造成了visitor模式的一个缺陷:新加入一个结点的时候都要添加visitor中的对其进行访问接口函数,这样使得所有的visitor及其派生类都要重新编译了,也就是说visitor模式一个缺点就是添加新的结点十分困难。另外,还需要指出的是visitor模式采用了所谓的”双分派”技术。

[设计模式]Visitor访问者模式_第2张图片

小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 

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 
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的接口,使得可以提供给这个新增加的子类的访问机制。

与其他设计模式关系

  • 与“迭代器模式”联用。因为访问者模式中的“对象结构”是一个包含元素角色的容器,当访问者遍历容器中的所有元素时,常常要用迭代器。
  • 访问者(Visitor)模式同“组合模式”联用。因为访问者(Visitor)模式中的“元素对象”可能是叶子对象或者是容器对象,如果元素对象包含容器对象,就必须用到组合模式。
  • 可以将访问者模式视为命令模式的加强版本, 其对象可对不同类的多种对象执行操作。

     

你可能感兴趣的:(模式架构)