C++访问者模式
·
访问者模式(Visitor Pattern)是一种行为型设计模式,它允许你在不修改现有类的情况下,为类层次结构中的元素添加新的操作。这种模式通过将操作封装在访问者对象中,实现了数据结构与操作的分离,使得操作可以独立于元素结构而变化。
访问者模式的核心角色
- 访问者(Visitor):定义一个访问每个元素的接口,为每个具体元素类对应一个访问操作
- 具体访问者(Concrete Visitor):实现访问者接口,定义对每个元素的具体操作
- 元素(Element):定义一个接受访问者的接口,通常是
accept()方法 - 具体元素(Concrete Element):实现元素接口,通过调用访问者的对应方法来接受访问
- 对象结构(Object Structure):持有元素集合,提供遍历元素的方法,允许访问者访问其元素
访问者模式的实现示例
下面以"文档编辑器"为例展示访问者模式的实现,文档中包含文本、图片和表格等元素,我们可以通过不同的访问者实现不同的操作(如导出为HTML、统计字数等):
#include <iostream>
#include <string>
#include <vector>
#include <memory>
// 前向声明
class TextElement;
class ImageElement;
class TableElement;
// 访问者接口
class Visitor {
public:
virtual void visit(TextElement* element) = 0;
virtual void visit(ImageElement* element) = 0;
virtual void visit(TableElement* element) = 0;
virtual ~Visitor() = default;
};
// 元素接口
class Element {
public:
virtual void accept(Visitor* visitor) = 0;
virtual ~Element() = default;
};
// 具体元素:文本
class TextElement : public Element {
private:
std::string content;
int wordCount;
public:
TextElement(const std::string& text, int words)
: content(text), wordCount(words) {}
std::string getContent() const { return content; }
int getWordCount() const { return wordCount; }
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
// 具体元素:图片
class ImageElement : public Element {
private:
std::string filename;
int width;
int height;
public:
ImageElement(const std::string& name, int w, int h)
: filename(name), width(w), height(h) {}
std::string getFilename() const { return filename; }
int getWidth() const { return width; }
int getHeight() const { return height; }
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
// 具体元素:表格
class TableElement : public Element {
private:
std::string title;
int rows;
int cols;
public:
TableElement(const std::string& t, int r, int c)
: title(t), rows(r), cols(c) {}
std::string getTitle() const { return title; }
int getRows() const { return rows; }
int getCols() const { return cols; }
void accept(Visitor* visitor) override {
visitor->visit(this);
}
};
// 具体访问者:HTML导出器
class HtmlExporter : public Visitor {
public:
void visit(TextElement* element) override {
std::cout << "<p>" << element->getContent() << "</p>" << std::endl;
}
void visit(ImageElement* element) override {
std::cout << "<img src=\"" << element->getFilename()
<< "\" width=\"" << element->getWidth()
<< "\" height=\"" << element->getHeight() << "\">" << std::endl;
}
void visit(TableElement* element) override {
std::cout << "<table title=\"" << element->getTitle() << "\">" << std::endl;
std::cout << " <!-- 表格内容,共 " << element->getRows()
<< " 行 " << element->getCols() << " 列 -->" << std::endl;
std::cout << "</table>" << std::endl;
}
};
// 具体访问者:统计器
class StatisticsVisitor : public Visitor {
private:
int totalWords;
int imageCount;
int tableCount;
int totalCells;
public:
StatisticsVisitor()
: totalWords(0), imageCount(0), tableCount(0), totalCells(0) {}
void visit(TextElement* element) override {
totalWords += element->getWordCount();
}
void visit(ImageElement* element) override {
imageCount++;
}
void visit(TableElement* element) override {
tableCount++;
totalCells += element->getRows() * element->getCols();
}
void showStatistics() const {
std::cout << "\n=== 文档统计信息 ===" << std::endl;
std::cout << "总字数: " << totalWords << std::endl;
std::cout << "图片数量: " << imageCount << std::endl;
std::cout << "表格数量: " << tableCount << std::endl;
std::cout << "表格总单元格数: " << totalCells << std::endl;
}
};
// 对象结构:文档
class Document {
private:
std::vector<std::unique_ptr<Element>> elements;
public:
void addElement(std::unique_ptr<Element> element) {
elements.push_back(std::move(element));
}
// 接受访问者,遍历所有元素
void accept(Visitor* visitor) {
for (const auto& element : elements) {
element->accept(visitor);
}
}
};
// 客户端使用
int main() {
// 创建文档并添加元素
Document doc;
doc.addElement(std::make_unique<TextElement>("这是一段示例文本", 10));
doc.addElement(std::make_unique<ImageElement>("photo.jpg", 800, 600));
doc.addElement(std::make_unique<TableElement>("数据表格", 5, 3));
doc.addElement(std::make_unique<TextElement>("这是另一段文本", 8));
// 使用HTML导出器访问文档
std::cout << "=== HTML导出结果 ===" << std::endl;
HtmlExporter htmlExporter;
doc.accept(&htmlExporter);
// 使用统计器访问文档
StatisticsVisitor statsVisitor;
doc.accept(&statsVisitor);
statsVisitor.showStatistics();
return 0;
}
访问者模式的工作原理
- 元素类提供
accept()方法,该方法接收一个访问者对象并调用访问者的对应visit()方法 - 每个具体元素的
accept()方法会调用访问者中与自己类型匹配的visit()方法 - 访问者通过
visit()方法的参数获取元素的具体类型,并对元素执行相应操作 - 对象结构(如文档)负责管理元素集合,并提供方法让访问者可以遍历所有元素
访问者模式的双重分派
访问者模式通过"双重分派"机制实现了操作与元素类型的绑定:
- 第一次分派:客户端调用元素的
accept()方法 - 第二次分派:元素的
accept()方法调用访问者的对应visit()方法
这种机制使得访问者可以根据元素的具体类型执行不同的操作,而无需使用大量的条件判断。
访问者模式的应用场景
- 当需要为一个包含多个不同类型元素的对象结构添加新操作时
- 当对象结构相对稳定,但需要经常为其添加新操作时
- 当需要在不修改元素类的前提下,对元素进行多种不同且不相关的操作时
访问者模式的优缺点
优点:
- 分离了数据结构与操作,使操作可以独立演化
- 便于添加新的操作,只需新增访问者类,符合开放-封闭原则
- 将相关操作集中到一个访问者类中,而不是分散在各个元素类中
- 可以在访问过程中收集元素的信息(如统计器示例)
缺点:
- 增加了新元素类型的难度,需要修改所有访问者类
- 破坏了元素的封装性,访问者需要了解元素的内部细节才能执行操作
- 如果对象结构经常变化(添加新元素),访问者模式会变得难以维护
访问者模式在C++的STL中也有间接应用,例如std::for_each算法可以看作是一种简化的访问者模式。在实际开发中,访问者模式常用于编译器的语法树分析、文档解析、复杂集合的多种遍历操作等场景。
更多推荐


所有评论(0)