设计模式(C++)-行为型模式-访问者模式
一、访问者模式概述
访问者模式是一种行为型设计模式,允许在不修改已有类结构的情况下定义新的操作。它将算法与对象结构分离,使得可以在不改变各元素类的前提下定义作用于这些元素的新操作。
核心思想:双重分派:1. 元素对象接受访问者;2.访问者访问具体元素
- 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),
二、访问者模式UML类图
访问者模式场景
假设我们有一个简单的文档编辑器,它包含不同类型的元素,如Paragraph和Header。我们希望添加一个功能,可以访问这些元素并执行某些操作。在这个示例中,我们定义了一个Visitor接口,它有两个纯虚函数visit,分别用于访Paragraph和Header对象。ConcreteVisitor类实现了Visitor接口,并为每种元素提供了具体的操作。Element是一个抽象基类,它定义了一个accept方法,这个方法接受一个Visitor对象。Paragraph和Header类继承自Element,并实现了accept方法,该方法调用访问者的visit方法。Document类是一个对象结构,它包含一个元素列表,并且有一个accept方法,该方法
遍历所有元素并调用它们的accept方法。最后,在main函数中,我们创建了一个Document对象,添加了一些元素,并使用ConcreteVisitor来访问这些元素。这个示例展示了如何在C++中使用Visitor模式来对一个对象结构中的元素执行不同的操作,而不需要修改这些元素的类。

三、代码实现
cpp
//visitor.h
#pragma once
/*
Visitor 模式(访问者模式)
- 访问者模式(VisitorPattern),封装一些作用于某种数据结构的各元素的操作,它可以在不改变
数据结构的前提下定义作用于这些元素的新的操作。
- 主要将数据结构与数据操作分离,解决 数据结构和操作耦合性问题
- 访问者模式的基本工作原理是:在被访问的类里面加一个对外提供接待访问者的接口
- 访问者模式主要应用场景是:需要对一个对象结构中的对象进行很多不同操作(这些操作彼此没有关联),
同时需要避免让这些操作"污染"这些对象的类,可以选用访问者模式解决
特别适用于当你需要对一个对象集合执行多种不同的操作,而又不想在每个对象类中都添加这些操作时。
核心概念:
Visitor(访问者):定义了一个访问者接口,声明了对每一个元素类访问的方法。
ConcreteVisitor(具体访问者):实现Visitor接口,为每一个元素类提供相应的处理。
Element(元素):定义了一个接受访问者的接口。
ConcreteElement(具体元素):实现Element接口,为访问者提供接收访问的方法。
ObjectStructure(对象结构):是一个包含元素的容器,可以遍历元素,允许访问者访问每一个元素。
应用场景:
当一个对象结构中的对象需要执行多种不同的操作,而这些操作可能在未来会不断增加时。
当这些操作与对象结构中的对象有紧密的耦合关系,但又能独立于对象结构变化时
示例:
假设我们有一个简单的文档编辑器,它包含不同类型的元素,如Paragraph和Header。我们希望添加一个功能,可以访问这些元素并执行某些操作。
在这个示例中,我们定义了一个Visitor接口,它有两个纯虚函数visit,分别用于访问Paragraph和Header对象。ConcreteVisitor类实现了Visitor接口,
并为每种元素提供了具体的操作。Element是一个抽象基类,它定义了一个accept方法,这个方法接受一个Visitor对象。Paragraph和Header类继承自
Element,并实现了accept方法,该方法调用访问者的visit方法。Document类是一个对象结构,它包含一个元素列表,并且有一个accept方法,该方法
遍历所有元素并调用它们的accept方法。最后,在main函数中,我们创建了一个Document对象,添加了一些元素,并使用ConcreteVisitor来访问这些
元素。这个示例展示了如何在C++中使用Visitor模式来对一个对象结构中的元素执行不同的操作,而不需要修改这些元素的类。
*/
#include <iostream>
#include <vector>
#include <memory>
using namespace std;
class Paragraph;
class Header;
class Visitor {
public:
~Visitor() {};
virtual void visit(Paragraph*p) = 0;
virtual void visit(Header*h) = 0;
};
//定义具体的访问者
class ConcreteVisitor :public Visitor {
public:
void visit(Paragraph*p);
void visit(Header*h);
};
//定义元素接口
class Element {
public:
virtual ~Element() {};
virtual void accept(Visitor*v) = 0;
};
class Paragraph :public Element {
public:
Paragraph(const string&content) :m_content(content) {};
void accept(Visitor*v);
string getContent()const;
private:
string m_content;
};
class Header :public Element {
public:
Header(const string&content) :m_content(content) {};
void accept(Visitor*v);
string getContent()const;
private:
string m_content;
};
//定义对象结构
class Document{
public:
void addElement(std::unique_ptr<Element>elem);
void accept(Visitor*v);
private:
vector<std::unique_ptr<Element> > m_elements;
};
void testVisitor();
//visitor.cc
#include "visitor.h"
void ConcreteVisitor::visit(Paragraph*p) {
cout << "Visiting a paragraph with content: " << p->getContent().c_str() << endl;
}
void ConcreteVisitor::visit(Header*h) {
cout << "Visiting a Header with content: " << h->getContent().c_str() << endl;
}
void Paragraph::accept(Visitor*v) {
v->visit(this);
}
string Paragraph::getContent()const {
return m_content;
}
void Header::accept(Visitor*v) {
v->visit(this);
}
string Header::getContent()const {
return m_content;
}
void Document::addElement(std::unique_ptr<Element>elem) {
m_elements.emplace_back(elem.release());
}
void Document::accept(Visitor*v) {
for (auto &elem:m_elements)
{
elem->accept(v);
}
}
void testVisitor(){
cout << "=================visitor start===============" << endl;
Document doc;
doc.addElement(std::make_unique<Paragraph>("This is a paragraph."));
doc.addElement(std::make_unique<Header>("This is a header."));
ConcreteVisitor visitor;
doc.accept(&visitor);
cout << "=================visitor end===============" << endl;
}
四、优缺点总结
优点:
- 开闭原则:新增操作容易,无需修改元素类
- 单一职责原则:相关行为集中在访问者中
- 跨元素操作:可以在一个访问者中实现跨不同元素的操作
- 算法集中:相关算法集中在一个类中
- 状态累积:访问者可以在访问过程中积累状态
缺点:
- 破坏封装:访问者需要公开元素内部细节
- 增加新元素困难:每增加新元素,需要修改所有的访问者
- 难以访问私有成员:需要使用友元或公开方法
- 元素依赖访问者:元素必须了解访问者接口