第二十章:遍历万象,操作随心------Visitor的访问艺术
风云再起,访问学者登场
在State展示完他那精妙的状态艺术后,Visitor彬彬有礼地走出,向复杂的对象结构行礼。他的举止优雅从容,仿佛一位学识渊博的学者在审视着精密的学术体系。
"State兄的状态管理确实精妙,"Visitor优雅地说道,"但在不修改现有对象结构的前提下定义新操作方面,需要更加灵活的访问方式。诸位请看------"
Visitor的身形在几个不同的对象结构间穿梭,却始终保持着适当的距离:"我的访问者模式,专为解决算法与对象结构分离问题而生!我允许你在不修改现有对象结构的前提下,定义作用于这些元素的新操作!"
架构老人眼中闪过赞许之色:"善!Visitor,就请你为大家展示这访问艺术的精妙所在。"
访问者模式的核心要义
Visitor面向众人,开始阐述他的武学真谛:
"在我的访问者模式中,主要包含两个核心角色:"
"Visitor(访问者):为对象结构中的每个ConcreteElement类声明一个Visit操作。"
"ConcreteVisitor(具体访问者):实现每个由Visitor声明的操作。"
"Element(元素):定义一个Accept操作,它以一个访问者为参数。"
"ConcreteElement(具体元素):实现Accept操作。"
"其精妙之处在于,"Visitor继续道,"我将算法与对象结构分离,使得可以在不修改现有元素类的情况下增加新的操作。访问者可以累积状态,将有关状态存储在其内部!"
C++实战:文档处理系统
"且让我以一个文档处理系统为例,展示访问者模式的实战应用。"Visitor说着,手中凝聚出一道道代码流光。
基础框架搭建
首先,Visitor定义了文档元素和访问者接口:
cpp
#include <iostream>
#include <vector>
#include <string>
#include <memory>
#include <algorithm>
#include <map>
#include <iomanip>
#include <sstream>
#include <random>
#include <thread>
#include <chrono>
// 前向声明
class TextElement;
class ImageElement;
class TableElement;
class ParagraphElement;
// 访问者接口
class DocumentVisitor {
public:
virtual ~DocumentVisitor() = default;
// 访问各种文档元素的方法
virtual void visit(TextElement* element) = 0;
virtual void visit(ImageElement* element) = 0;
virtual void visit(TableElement* element) = 0;
virtual void visit(ParagraphElement* element) = 0;
// 访问者信息
virtual std::string getVisitorName() const = 0;
virtual std::string getDescription() const = 0;
// 访问者状态管理
virtual void reset() = 0;
virtual std::string getResults() const = 0;
};
// 文档元素接口
class DocumentElement {
public:
virtual ~DocumentElement() = default;
// 接受访问者访问
virtual void accept(DocumentVisitor* visitor) = 0;
// 元素基本信息
virtual std::string getElementType() const = 0;
virtual std::string getId() const = 0;
virtual int getSize() const = 0; // 估算大小
virtual std::string getContentPreview() const = 0;
// 元素位置信息
virtual void setPosition(int x, int y) = 0;
virtual std::pair<int, int> getPosition() const = 0;
// 元素样式
virtual void setStyle(const std::string& style) = 0;
virtual std::string getStyle() const = 0;
};
具体元素实现
Visitor展示了各种文档元素的具体实现:
cpp
// 具体元素:文本元素
class TextElement : public DocumentElement {
private:
std::string id_;
std::string content_;
std::pair<int, int> position_;
std::string style_;
int fontSize_;
std::string fontFamily_;
public:
TextElement(const std::string& id, const std::string& content,
int x = 0, int y = 0, const std::string& style = "normal")
: id_(id), content_(content), position_({x, y}), style_(style),
fontSize_(12), fontFamily_("Arial") {
std::cout << "📝 创建文本元素: " << id_ << std::endl;
}
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
}
std::string getElementType() const override { return "Text"; }
std::string getId() const override { return id_; }
int getSize() const override {
return content_.length() * 2; // 估算大小
}
std::string getContentPreview() const override {
std::string preview = content_;
if (preview.length() > 20) {
preview = preview.substr(0, 17) + "...";
}
return "文本: \"" + preview + "\"";
}
void setPosition(int x, int y) override {
position_ = {x, y};
}
std::pair<int, int> getPosition() const override {
return position_;
}
void setStyle(const std::string& style) override {
style_ = style;
}
std::string getStyle() const override {
return style_;
}
// 文本元素特有方法
std::string getContent() const { return content_; }
void setContent(const std::string& content) { content_ = content; }
int getFontSize() const { return fontSize_; }
void setFontSize(int size) { fontSize_ = size; }
std::string getFontFamily() const { return fontFamily_; }
void setFontFamily(const std::string& font) { fontFamily_ = font; }
int getWordCount() const {
std::istringstream iss(content_);
return std::distance(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>());
}
std::string getDetailedInfo() const {
std::stringstream ss;
ss << "文本元素[" << id_ << "] 位置:(" << position_.first << "," << position_.second
<< ") 字数:" << getWordCount() << " 样式:" << style_;
return ss.str();
}
};
// 具体元素:图片元素
class ImageElement : public DocumentElement {
private:
std::string id_;
std::string imagePath_;
std::pair<int, int> position_;
std::string style_;
int width_;
int height_;
std::string format_;
double fileSizeMB_;
public:
ImageElement(const std::string& id, const std::string& path,
int width = 100, int height = 100, double fileSize = 1.0)
: id_(id), imagePath_(path), position_({0, 0}), style_("normal"),
width_(width), height_(height), format_("JPEG"), fileSizeMB_(fileSize) {
std::cout << "🖼️ 创建图片元素: " << id_ << std::endl;
}
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
}
std::string getElementType() const override { return "Image"; }
std::string getId() const override { return id_; }
int getSize() const override {
return static_cast<int>(fileSizeMB_ * 1024); // KB
}
std::string getContentPreview() const override {
return "图片: " + imagePath_ + " [" + std::to_string(width_) + "x" +
std::to_string(height_) + "]";
}
void setPosition(int x, int y) override {
position_ = {x, y};
}
std::pair<int, int> getPosition() const override {
return position_;
}
void setStyle(const std::string& style) override {
style_ = style;
}
std::string getStyle() const override {
return style_;
}
// 图片元素特有方法
std::string getImagePath() const { return imagePath_; }
void setImagePath(const std::string& path) { imagePath_ = path; }
int getWidth() const { return width_; }
void setWidth(int width) { width_ = width; }
int getHeight() const { return height_; }
void setHeight(int height) { height_ = height; }
std::string getFormat() const { return format_; }
void setFormat(const std::string& format) { format_ = format; }
double getFileSizeMB() const { return fileSizeMB_; }
void setFileSizeMB(double size) { fileSizeMB_ = size; }
double getAspectRatio() const {
return static_cast<double>(width_) / height_;
}
std::string getDetailedInfo() const {
std::stringstream ss;
ss << "图片元素[" << id_ << "] 路径:" << imagePath_
<< " 尺寸:" << width_ << "x" << height_
<< " 大小:" << std::fixed << std::setprecision(2) << fileSizeMB_ << "MB";
return ss.str();
}
};
// 具体元素:表格元素
class TableElement : public DocumentElement {
private:
std::string id_;
std::vector<std::vector<std::string>> data_;
std::pair<int, int> position_;
std::string style_;
int rows_;
int columns_;
std::string title_;
public:
TableElement(const std::string& id, int rows = 3, int columns = 3,
const std::string& title = "")
: id_(id), position_({0, 0}), style_("normal"),
rows_(rows), columns_(columns), title_(title) {
// 初始化表格数据
data_.resize(rows);
for (int i = 0; i < rows; ++i) {
data_[i].resize(columns);
for (int j = 0; j < columns; ++j) {
data_[i][j] = "Cell(" + std::to_string(i) + "," + std::to_string(j) + ")";
}
}
std::cout << "📊 创建表格元素: " << id_ << " [" << rows_ << "x" << columns_ << "]" << std::endl;
}
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
}
std::string getElementType() const override { return "Table"; }
std::string getId() const override { return id_; }
int getSize() const override {
int totalChars = 0;
for (const auto& row : data_) {
for (const auto& cell : row) {
totalChars += cell.length();
}
}
return totalChars;
}
std::string getContentPreview() const override {
return "表格: " + title_ + " [" + std::to_string(rows_) + "x" +
std::to_string(columns_) + "]";
}
void setPosition(int x, int y) override {
position_ = {x, y};
}
std::pair<int, int> getPosition() const override {
return position_;
}
void setStyle(const std::string& style) override {
style_ = style;
}
std::string getStyle() const override {
return style_;
}
// 表格元素特有方法
void setCell(int row, int col, const std::string& value) {
if (row >= 0 && row < rows_ && col >= 0 && col < columns_) {
data_[row][col] = value;
}
}
std::string getCell(int row, int col) const {
if (row >= 0 && row < rows_ && col >= 0 && col < columns_) {
return data_[row][col];
}
return "";
}
int getRowCount() const { return rows_; }
int getColumnCount() const { return columns_; }
std::string getTitle() const { return title_; }
void setTitle(const std::string& title) { title_ = title; }
int getTotalCells() const {
return rows_ * columns_;
}
std::string getDetailedInfo() const {
std::stringstream ss;
ss << "表格元素[" << id_ << "] 标题:" << title_
<< " 尺寸:" << rows_ << "x" << columns_
<< " 总单元格:" << getTotalCells();
return ss.str();
}
};
// 具体元素:段落元素
class ParagraphElement : public DocumentElement {
private:
std::string id_;
std::vector<std::shared_ptr<DocumentElement>> children_;
std::pair<int, int> position_;
std::string style_;
std::string alignment_;
int lineSpacing_;
public:
ParagraphElement(const std::string& id, const std::string& alignment = "left")
: id_(id), position_({0, 0}), style_("normal"),
alignment_(alignment), lineSpacing_(1) {
std::cout << "📄 创建段落元素: " << id_ << std::endl;
}
void accept(DocumentVisitor* visitor) override {
visitor->visit(this);
// 让访问者访问所有子元素
for (auto& child : children_) {
child->accept(visitor);
}
}
std::string getElementType() const override { return "Paragraph"; }
std::string getId() const override { return id_; }
int getSize() const override {
int totalSize = 0;
for (const auto& child : children_) {
totalSize += child->getSize();
}
return totalSize;
}
std::string getContentPreview() const override {
return "段落: " + std::to_string(children_.size()) + " 个子元素";
}
void setPosition(int x, int y) override {
position_ = {x, y};
}
std::pair<int, int> getPosition() const override {
return position_;
}
void setStyle(const std::string& style) override {
style_ = style;
}
std::string getStyle() const override {
return style_;
}
// 段落元素特有方法
void addChild(std::shared_ptr<DocumentElement> child) {
children_.push_back(child);
}
void removeChild(const std::string& childId) {
children_.erase(
std::remove_if(children_.begin(), children_.end(),
[&childId](const std::shared_ptr<DocumentElement>& child) {
return child->getId() == childId;
}),
children_.end()
);
}
std::vector<std::shared_ptr<DocumentElement>> getChildren() const {
return children_;
}
std::string getAlignment() const { return alignment_; }
void setAlignment(const std::string& alignment) { alignment_ = alignment; }
int getLineSpacing() const { return lineSpacing_; }
void setLineSpacing(int spacing) { lineSpacing_ = spacing; }
int getChildCount() const {
return children_.size();
}
std::string getDetailedInfo() const {
std::stringstream ss;
ss << "段落元素[" << id_ << "] 子元素数:" << getChildCount()
<< " 对齐:" << alignment_ << " 行距:" << lineSpacing_;
return ss.str();
}
};
具体访问者实现
Visitor展示了各种文档处理操作的具体实现:
cpp
// 具体访问者:文档统计访问者
class DocumentStatsVisitor : public DocumentVisitor {
private:
int textCount_;
int imageCount_;
int tableCount_;
int paragraphCount_;
int totalSize_;
int totalWords_;
std::vector<std::string> visitedElements_;
public:
DocumentStatsVisitor()
: textCount_(0), imageCount_(0), tableCount_(0), paragraphCount_(0),
totalSize_(0), totalWords_(0) {
std::cout << "📊 创建文档统计访问者" << std::endl;
}
void visit(TextElement* element) override {
textCount_++;
totalSize_ += element->getSize();
totalWords_ += element->getWordCount();
visitedElements_.push_back("文本: " + element->getId());
std::cout << " 📝 统计文本: " << element->getId()
<< " (字数:" << element->getWordCount() << ")" << std::endl;
}
void visit(ImageElement* element) override {
imageCount_++;
totalSize_ += element->getSize();
visitedElements_.push_back("图片: " + element->getId());
std::cout << " 🖼️ 统计图片: " << element->getId()
<< " (大小:" << std::fixed << std::setprecision(2)
<< element->getFileSizeMB() << "MB)" << std::endl;
}
void visit(TableElement* element) override {
tableCount_++;
totalSize_ += element->getSize();
visitedElements_.push_back("表格: " + element->getId());
std::cout << " 📊 统计表格: " << element->getId()
<< " (单元格:" << element->getTotalCells() << ")" << std::endl;
}
void visit(ParagraphElement* element) override {
paragraphCount_++;
visitedElements_.push_back("段落: " + element->getId());
std::cout << " 📄 统计段落: " << element->getId()
<< " (子元素:" << element->getChildCount() << ")" << std::endl;
}
std::string getVisitorName() const override {
return "文档统计访问者";
}
std::string getDescription() const override {
return "统计文档中各种元素的数量和大小";
}
void reset() override {
textCount_ = imageCount_ = tableCount_ = paragraphCount_ = 0;
totalSize_ = totalWords_ = 0;
visitedElements_.clear();
std::cout << "🔄 重置文档统计" << std::endl;
}
std::string getResults() const override {
std::stringstream ss;
ss << "📊 文档统计结果:" << std::endl;
ss << " 文本元素: " << textCount_ << " 个" << std::endl;
ss << " 图片元素: " << imageCount_ << " 个" << std::endl;
ss << " 表格元素: " << tableCount_ << " 个" << std::endl;
ss << " 段落元素: " << paragraphCount_ << " 个" << std::endl;
ss << " 总字数: " << totalWords_ << " 个" << std::endl;
ss << " 总大小: " << totalSize_ << " 单位" << std::endl;
ss << " 访问元素总数: " << visitedElements_.size() << " 个" << std::endl;
return ss.str();
}
// 统计访问者特有方法
void showDetailedStats() const {
std::cout << "\n📈 详细统计信息:" << std::endl;
std::cout << getResults();
std::cout << "\n📋 访问的元素列表:" << std::endl;
for (size_t i = 0; i < visitedElements_.size(); ++i) {
std::cout << " " << (i + 1) << ". " << visitedElements_[i] << std::endl;
}
}
double getAverageWordsPerText() const {
return textCount_ > 0 ? static_cast<double>(totalWords_) / textCount_ : 0.0;
}
};
// 具体访问者:文档渲染访问者
class DocumentRenderVisitor : public DocumentVisitor {
private:
std::vector<std::string> renderLog_;
int renderTimeMs_;
public:
DocumentRenderVisitor() : renderTimeMs_(0) {
std::cout << "🎨 创建文档渲染访问者" << std::endl;
}
void visit(TextElement* element) override {
auto startTime = std::chrono::high_resolution_clock::now();
std::string renderInfo = "渲染文本: " + element->getId() +
" [字体:" + element->getFontFamily() +
" 大小:" + std::to_string(element->getFontSize()) +
" 样式:" + element->getStyle() + "]";
renderLog_.push_back(renderInfo);
// 模拟渲染过程
std::this_thread::sleep_for(std::chrono::milliseconds(50));
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
renderTimeMs_ += duration.count();
std::cout << " 📝 " << renderInfo << " (耗时:" << duration.count() << "ms)" << std::endl;
}
void visit(ImageElement* element) override {
auto startTime = std::chrono::high_resolution_clock::now();
std::string renderInfo = "渲染图片: " + element->getId() +
" [尺寸:" + std::to_string(element->getWidth()) +
"x" + std::to_string(element->getHeight()) +
" 格式:" + element->getFormat() + "]";
renderLog_.push_back(renderInfo);
// 模拟渲染过程(图片渲染通常更耗时)
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
renderTimeMs_ += duration.count();
std::cout << " 🖼️ " << renderInfo << " (耗时:" << duration.count() << "ms)" << std::endl;
}
void visit(TableElement* element) override {
auto startTime = std::chrono::high_resolution_clock::now();
std::string renderInfo = "渲染表格: " + element->getId() +
" [" + std::to_string(element->getRowCount()) +
"x" + std::to_string(element->getColumnCount()) + "]";
renderLog_.push_back(renderInfo);
// 模拟渲染过程
std::this_thread::sleep_for(std::chrono::milliseconds(80));
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
renderTimeMs_ += duration.count();
std::cout << " 📊 " << renderInfo << " (耗时:" << duration.count() << "ms)" << std::endl;
}
void visit(ParagraphElement* element) override {
auto startTime = std::chrono::high_resolution_clock::now();
std::string renderInfo = "渲染段落: " + element->getId() +
" [对齐:" + element->getAlignment() +
" 行距:" + std::to_string(element->getLineSpacing()) + "]";
renderLog_.push_back(renderInfo);
// 模拟渲染过程
std::this_thread::sleep_for(std::chrono::milliseconds(30));
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
renderTimeMs_ += duration.count();
std::cout << " 📄 " << renderInfo << " (耗时:" << duration.count() << "ms)" << std::endl;
}
std::string getVisitorName() const override {
return "文档渲染访问者";
}
std::string getDescription() const override {
return "渲染文档中的各种元素";
}
void reset() override {
renderLog_.clear();
renderTimeMs_ = 0;
std::cout << "🔄 重置渲染状态" << std::endl;
}
std::string getResults() const override {
std::stringstream ss;
ss << "🎨 文档渲染结果:" << std::endl;
ss << " 渲染元素总数: " << renderLog_.size() << " 个" << std::endl;
ss << " 总渲染时间: " << renderTimeMs_ << "ms" << std::endl;
ss << " 平均渲染时间: " << (renderLog_.empty() ? 0 : renderTimeMs_ / renderLog_.size()) << "ms/元素" << std::endl;
return ss.str();
}
// 渲染访问者特有方法
void showRenderLog() const {
std::cout << "\n📋 渲染日志 (" << renderLog_.size() << " 条记录):" << std::endl;
for (size_t i = 0; i < renderLog_.size(); ++i) {
std::cout << " " << (i + 1) << ". " << renderLog_[i] << std::endl;
}
}
int getTotalRenderTime() const {
return renderTimeMs_;
}
};
UML 武功秘籍图
accepts visits <<interface>> DocumentVisitor +visit(TextElement*) : void +visit(ImageElement*) : void +visit(TableElement*) : void +visit(ParagraphElement*) : void +getVisitorName() : string +getDescription() : string +reset() : void +getResults() : string <<interface>> DocumentElement +accept(DocumentVisitor*) : void +getElementType() : string +getId() : string +getSize() : int +getContentPreview() : string +setPosition(int, int) : void +getPosition() +setStyle(string) : void +getStyle() : string TextElement -string id_ -string content_ -pair<int,int> position_ -string style_ -int fontSize_ -string fontFamily_ +accept(DocumentVisitor*) : void +getElementType() : string +getId() : string +getSize() : int +getContentPreview() : string +getWordCount() : int +getDetailedInfo() : string DocumentStatsVisitor -int textCount_ -int imageCount_ -int tableCount_ -int paragraphCount_ -int totalSize_ -int totalWords_ -vector<string> visitedElements_ +visit(TextElement*) : void +visit(ImageElement*) : void +visit(TableElement*) : void +visit(ParagraphElement*) : void +getVisitorName() : string +getDescription() : string +reset() : void +getResults() : string +showDetailedStats() : void DocumentRenderVisitor -vector<string> renderLog_ -int renderTimeMs_ +visit(TextElement*) : void +visit(ImageElement*) : void +visit(TableElement*) : void +visit(ParagraphElement*) : void +getVisitorName() : string +getDescription() : string +reset() : void +getResults() : string +showRenderLog() : void ImageElement TableElement ParagraphElement
实战演练:高级访问系统
Visitor继续展示更复杂的访问者模式应用:
cpp
// 具体访问者:文档导出访问者
class DocumentExportVisitor : public DocumentVisitor {
private:
std::string exportFormat_;
std::vector<std::string> exportLog_;
int exportedElements_;
std::string outputPath_;
public:
DocumentExportVisitor(const std::string& format = "PDF", const std::string& output = "./export")
: exportFormat_(format), exportedElements_(0), outputPath_(output) {
std::cout << "💾 创建文档导出访问者,格式: " << exportFormat_ << std::endl;
}
void visit(TextElement* element) override {
std::string exportInfo = "导出文本: " + element->getId() +
" 到 " + outputPath_ + "/" + element->getId() + ".txt";
exportLog_.push_back(exportInfo);
exportedElements_++;
// 模拟导出过程
std::this_thread::sleep_for(std::chrono::milliseconds(40));
std::cout << " 📝 " << exportInfo << std::endl;
}
void visit(ImageElement* element) override {
std::string exportInfo = "导出图片: " + element->getId() +
" 到 " + outputPath_ + "/" + element->getId() + "." +
element->getFormat().substr(0, 3);
exportLog_.push_back(exportInfo);
exportedElements_++;
// 模拟导出过程
std::this_thread::sleep_for(std::chrono::milliseconds(120));
std::cout << " 🖼️ " << exportInfo << std::endl;
}
void visit(TableElement* element) override {
std::string exportInfo = "导出表格: " + element->getId() +
" 到 " + outputPath_ + "/" + element->getId() + ".csv";
exportLog_.push_back(exportInfo);
exportedElements_++;
// 模拟导出过程
std::this_thread::sleep_for(std::chrono::milliseconds(90));
std::cout << " 📊 " << exportInfo << std::endl;
}
void visit(ParagraphElement* element) override {
std::string exportInfo = "导出段落: " + element->getId() +
" 到 " + outputPath_ + "/" + element->getId() + ".html";
exportLog_.push_back(exportInfo);
exportedElements_++;
// 模拟导出过程
std::this_thread::sleep_for(std::chrono::milliseconds(60));
std::cout << " 📄 " << exportInfo << std::endl;
}
std::string getVisitorName() const override {
return "文档导出访问者";
}
std::string getDescription() const override {
return "将文档元素导出为" + exportFormat_ + "格式";
}
void reset() override {
exportLog_.clear();
exportedElements_ = 0;
std::cout << "🔄 重置导出状态" << std::endl;
}
std::string getResults() const override {
std::stringstream ss;
ss << "💾 文档导出结果:" << std::endl;
ss << " 导出格式: " << exportFormat_ << std::endl;
ss << " 输出路径: " << outputPath_ << std::endl;
ss << " 导出元素总数: " << exportedElements_ << " 个" << std::endl;
return ss.str();
}
// 导出访问者特有方法
void setExportFormat(const std::string& format) {
exportFormat_ = format;
std::cout << "🔄 设置导出格式: " << exportFormat_ << std::endl;
}
void setOutputPath(const std::string& path) {
outputPath_ = path;
std::cout << "🔄 设置输出路径: " << outputPath_ << std::endl;
}
void showExportSummary() const {
std::cout << "\n📤 导出摘要:" << std::endl;
std::cout << getResults();
std::cout << "\n📋 导出文件列表:" << std::endl;
for (size_t i = 0; i < exportLog_.size(); ++i) {
std::cout << " " << (i + 1) << ". " << exportLog_[i] << std::endl;
}
}
};
// 具体访问者:拼写检查访问者
class SpellCheckVisitor : public DocumentVisitor {
private:
std::vector<std::string> spellingErrors_;
std::vector<std::string> checkedElements_;
int totalWordsChecked_;
public:
SpellCheckVisitor() : totalWordsChecked_(0) {
std::cout << "🔍 创建拼写检查访问者" << std::endl;
}
void visit(TextElement* element) override {
std::string text = element->getContent();
int wordCount = element->getWordCount();
totalWordsChecked_ += wordCount;
checkedElements_.push_back("检查文本: " + element->getId() + " (字数:" + std::to_string(wordCount) + ")");
// 模拟拼写检查(简化实现)
std::vector<std::string> errors = simulateSpellCheck(text);
for (const auto& error : errors) {
spellingErrors_.push_back("文本[" + element->getId() + "]: " + error);
}
std::cout << " 📝 检查文本: " << element->getId()
<< " (字数:" << wordCount << ", 错误:" << errors.size() << ")" << std::endl;
}
void visit(ImageElement* element) override {
checkedElements_.push_back("跳过图片: " + element->getId());
std::cout << " 🖼️ 跳过图片: " << element->getId() << " (不支持图片拼写检查)" << std::endl;
}
void visit(TableElement* element) override {
int tableWords = 0;
int tableErrors = 0;
// 检查表格中的每个单元格
for (int i = 0; i < element->getRowCount(); ++i) {
for (int j = 0; j < element->getColumnCount(); ++j) {
std::string cellContent = element->getCell(i, j);
std::vector<std::string> errors = simulateSpellCheck(cellContent);
tableErrors += errors.size();
// 估算单词数
std::istringstream iss(cellContent);
tableWords += std::distance(std::istream_iterator<std::string>(iss),
std::istream_iterator<std::string>());
}
}
totalWordsChecked_ += tableWords;
checkedElements_.push_back("检查表格: " + element->getId() + " (单元格:" + std::to_string(element->getTotalCells()) + ")");
if (tableErrors > 0) {
spellingErrors_.push_back("表格[" + element->getId() + "]: " + std::to_string(tableErrors) + " 个拼写错误");
}
std::cout << " 📊 检查表格: " << element->getId()
<< " (单元格:" << element->getTotalCells() << ", 错误:" << tableErrors << ")" << std::endl;
}
void visit(ParagraphElement* element) override {
checkedElements_.push_back("检查段落: " + element->getId() + " (子元素:" + std::to_string(element->getChildCount()) + ")");
std::cout << " 📄 检查段落: " << element->getId()
<< " (将递归检查所有子元素)" << std::endl;
// 注意:ParagraphElement的accept方法会递归调用子元素的accept
}
std::string getVisitorName() const override {
return "拼写检查访问者";
}
std::string getDescription() const override {
return "检查文档中的拼写错误";
}
void reset() override {
spellingErrors_.clear();
checkedElements_.clear();
totalWordsChecked_ = 0;
std::cout << "🔄 重置拼写检查状态" << std::endl;
}
std::string getResults() const override {
std::stringstream ss;
ss << "🔍 拼写检查结果:" << std::endl;
ss << " 检查元素总数: " << checkedElements_.size() << " 个" << std::endl;
ss << " 检查单词总数: " << totalWordsChecked_ << " 个" << std::endl;
ss << " 发现拼写错误: " << spellingErrors_.size() << " 个" << std::endl;
ss << " 错误率: " << std::fixed << std::setprecision(2)
<< (totalWordsChecked_ > 0 ? (spellingErrors_.size() * 100.0 / totalWordsChecked_) : 0) << "%" << std::endl;
return ss.str();
}
// 拼写检查访问者特有方法
void showSpellingErrors() const {
if (spellingErrors_.empty()) {
std::cout << "✅ 未发现拼写错误" << std::endl;
return;
}
std::cout << "\n❌ 拼写错误列表 (" << spellingErrors_.size() << " 个):" << std::endl;
for (size_t i = 0; i < spellingErrors_.size(); ++i) {
std::cout << " " << (i + 1) << ". " << spellingErrors_[i] << std::endl;
}
}
double getErrorRate() const {
return totalWordsChecked_ > 0 ? (spellingErrors_.size() * 100.0 / totalWordsChecked_) : 0.0;
}
private:
std::vector<std::string> simulateSpellCheck(const std::string& text) {
std::vector<std::string> errors;
// 简化的拼写检查逻辑
std::istringstream iss(text);
std::string word;
while (iss >> word) {
// 模拟检查:假设包含数字或大写的单词可能有错误
if (std::any_of(word.begin(), word.end(), ::isdigit) ||
std::any_of(word.begin(), word.end(), ::isupper)) {
errors.push_back("可疑单词: \"" + word + "\"");
}
}
return errors;
}
};
// 文档结构类
class Document {
private:
std::string title_;
std::vector<std::shared_ptr<DocumentElement>> elements_;
std::vector<std::shared_ptr<DocumentVisitor>> visitorHistory_;
public:
Document(const std::string& title) : title_(title) {
std::cout << "📚 创建文档: " << title_ << std::endl;
}
void addElement(std::shared_ptr<DocumentElement> element) {
elements_.push_back(element);
std::cout << "➕ 添加元素: " << element->getId() << " 到文档" << std::endl;
}
void removeElement(const std::string& elementId) {
elements_.erase(
std::remove_if(elements_.begin(), elements_.end(),
[&elementId](const std::shared_ptr<DocumentElement>& element) {
return element->getId() == elementId;
}),
elements_.end()
);
std::cout << "🗑️ 从文档中移除元素: " << elementId << std::endl;
}
void accept(DocumentVisitor* visitor) {
std::cout << "\n🎯 文档接受访问者: " << visitor->getVisitorName() << std::endl;
std::cout << " 描述: " << visitor->getDescription() << std::endl;
auto startTime = std::chrono::high_resolution_clock::now();
// 重置访问者状态
visitor->reset();
// 让访问者访问所有元素
for (auto& element : elements_) {
element->accept(visitor);
}
auto endTime = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(endTime - startTime);
std::cout << "✅ 访问完成,总耗时: " << duration.count() << "ms" << std::endl;
std::cout << visitor->getResults() << std::endl;
// 记录访问历史
// visitorHistory_.push_back(std::shared_ptr<DocumentVisitor>(visitor)); // 注意:这里需要适当的内存管理
}
void showDocumentInfo() const {
std::cout << "\n📄 文档信息: " << title_ << std::endl;
std::cout << " 元素总数: " << elements_.size() << " 个" << std::endl;
int totalSize = 0;
std::map<std::string, int> typeCounts;
for (const auto& element : elements_) {
totalSize += element->getSize();
typeCounts[element->getElementType()]++;
}
std::cout << " 总大小: " << totalSize << " 单位" << std::endl;
std::cout << " 元素类型分布:" << std::endl;
for (const auto& pair : typeCounts) {
std::cout << " • " << pair.first << ": " << pair.second << " 个" << std::endl;
}
}
void listAllElements() const {
std::cout << "\n📋 文档元素列表 (" << elements_.size() << " 个):" << std::endl;
for (size_t i = 0; i < elements_.size(); ++i) {
std::cout << " " << (i + 1) << ". " << elements_[i]->getContentPreview() << std::endl;
}
}
std::string getTitle() const { return title_; }
int getElementCount() const { return elements_.size(); }
// 批量操作
void applyToAllElements(std::function<void(DocumentElement*)> operation) {
for (auto& element : elements_) {
operation(element.get());
}
}
};
完整测试代码
cpp
// 测试访问者模式
void testVisitorPattern() {
std::cout << "=== 访问者模式测试开始 ===" << std::endl;
// 创建文档和元素
std::cout << "\n--- 创建测试文档 ---" << std::endl;
Document document("测试文档");
// 创建各种元素
auto text1 = std::make_shared<TextElement>("text1", "这是一个测试文本内容,用于演示访问者模式。");
auto text2 = std::make_shared<TextElement>("text2", "另一个文本元素,包含更多的文字内容。");
auto image1 = std::make_shared<ImageElement>("image1", "photo.jpg", 800, 600, 2.5);
auto image2 = std::make_shared<ImageElement>("image2", "diagram.png", 1200, 800, 1.8);
auto table1 = std::make_shared<TableElement>("table1", 4, 3, "数据表格");
table1->setCell(0, 0, "姓名");
table1->setCell(0, 1, "年龄");
table1->setCell(0, 2, "职业");
auto paragraph1 = std::make_shared<ParagraphElement>("paragraph1", "justified");
paragraph1->addChild(std::make_shared<TextElement>("child_text1", "段落中的第一个文本。"));
paragraph1->addChild(std::make_shared<TextElement>("child_text2", "段落中的第二个文本。"));
// 添加元素到文档
document.addElement(text1);
document.addElement(image1);
document.addElement(table1);
document.addElement(paragraph1);
document.addElement(text2);
document.addElement(image2);
// 显示文档信息
document.showDocumentInfo();
document.listAllElements();
// 测试各种访问者
std::cout << "\n--- 测试统计访问者 ---" << std::endl;
DocumentStatsVisitor statsVisitor;
document.accept(&statsVisitor);
statsVisitor.showDetailedStats();
std::cout << "\n--- 测试渲染访问者 ---" << std::endl;
DocumentRenderVisitor renderVisitor;
document.accept(&renderVisitor);
renderVisitor.showRenderLog();
std::cout << "\n--- 测试导出访问者 ---" << std::endl;
DocumentExportVisitor exportVisitor("PDF", "./exports");
document.accept(&exportVisitor);
exportVisitor.showExportSummary();
std::cout << "\n--- 测试拼写检查访问者 ---" << std::endl;
SpellCheckVisitor spellCheckVisitor;
document.accept(&spellCheckVisitor);
spellCheckVisitor.showSpellingErrors();
std::cout << "\n=== 基础访问者模式测试结束 ===" << std::endl;
}
// 测试复合元素结构
void testCompositeStructure() {
std::cout << "\n=== 复合结构测试开始 ===" << std::endl;
// 创建复杂的文档结构
Document complexDocument("复杂文档结构");
// 创建嵌套的段落结构
auto mainParagraph = std::make_shared<ParagraphElement>("main_para", "left");
auto subParagraph1 = std::make_shared<ParagraphElement>("sub_para1", "left");
subParagraph1->addChild(std::make_shared<TextElement>("sub_text1", "子段落一的文本内容。"));
subParagraph1->addChild(std::make_shared<ImageElement>("sub_image1", "icon.png", 32, 32, 0.1));
auto subParagraph2 = std::make_shared<ParagraphElement>("sub_para2", "right");
subParagraph2->addChild(std::make_shared<TextElement>("sub_text2", "子段落二的文本内容。"));
subParagraph2->addChild(std::make_shared<TableElement>("sub_table1", 2, 2));
mainParagraph->addChild(subParagraph1);
mainParagraph->addChild(subParagraph2);
complexDocument.addElement(mainParagraph);
complexDocument.addElement(std::make_shared<TextElement>("standalone_text", "独立的文本元素。"));
// 显示文档结构
complexDocument.showDocumentInfo();
// 测试访问者在复合结构上的行为
std::cout << "\n--- 在复合结构上测试统计访问者 ---" << std::endl;
DocumentStatsVisitor statsVisitor;
complexDocument.accept(&statsVisitor);
statsVisitor.showDetailedStats();
std::cout << "\n--- 在复合结构上测试拼写检查访问者 ---" << std::endl;
SpellCheckVisitor spellVisitor;
complexDocument.accept(&spellVisitor);
spellVisitor.showSpellingErrors();
std::cout << "\n=== 复合结构测试结束 ===" << std::endl;
}
// 测试访问者组合
void testVisitorCombination() {
std::cout << "\n=== 访问者组合测试开始 ===" << std::endl;
Document document("访问者组合测试");
// 添加测试元素
document.addElement(std::make_shared<TextElement>("text1", "第一个测试文本。"));
document.addElement(std::make_shared<ImageElement>("img1", "test.jpg", 400, 300, 1.2));
document.addElement(std::make_shared<TableElement>("table1", 3, 2, "测试表格"));
// 创建访问者组合
std::cout << "\n--- 顺序执行多个访问者 ---" << std::endl;
DocumentStatsVisitor statsVisitor;
DocumentRenderVisitor renderVisitor;
SpellCheckVisitor spellVisitor;
std::vector<DocumentVisitor*> visitors = {&statsVisitor, &renderVisitor, &spellVisitor};
for (auto visitor : visitors) {
document.accept(visitor);
std::cout << std::string(50, '-') << std::endl;
}
std::cout << "\n=== 访问者组合测试结束 ===" << std::endl;
}
// 实战应用:文档处理系统
class DocumentProcessingSystem {
private:
std::vector<Document> documents_;
std::map<std::string, std::unique_ptr<DocumentVisitor>> visitorRegistry_;
public:
DocumentProcessingSystem() {
std::cout << "🏢 创建文档处理系统" << std::endl;
initializeVisitorRegistry();
}
void initializeVisitorRegistry() {
// 注册各种访问者
visitorRegistry_["stats"] = std::make_unique<DocumentStatsVisitor>();
visitorRegistry_["render"] = std::make_unique<DocumentRenderVisitor>();
visitorRegistry_["export"] = std::make_unique<DocumentExportVisitor>();
visitorRegistry_["spellcheck"] = std::make_unique<SpellCheckVisitor>();
std::cout << "✅ 初始化 " << visitorRegistry_.size() << " 个访问者" << std::endl;
}
void createDocument(const std::string& title) {
documents_.emplace_back(title);
std::cout << "📄 创建文档: " << title << std::endl;
}
Document& getDocument(int index) {
if (index >= 0 && index < documents_.size()) {
return documents_[index];
}
throw std::out_of_range("文档索引越界");
}
void processDocument(int docIndex, const std::string& visitorType) {
if (docIndex < 0 || docIndex >= documents_.size()) {
std::cout << "❌ 无效的文档索引: " << docIndex << std::endl;
return;
}
auto it = visitorRegistry_.find(visitorType);
if (it == visitorRegistry_.end()) {
std::cout << "❌ 未知的访问者类型: " << visitorType << std::endl;
return;
}
std::cout << "\n🔧 处理文档 #" << docIndex << " 使用访问者: " << visitorType << std::endl;
documents_[docIndex].accept(it->second.get());
}
void batchProcess(const std::string& visitorType) {
auto it = visitorRegistry_.find(visitorType);
if (it == visitorRegistry_.end()) {
std::cout << "❌ 未知的访问者类型: " << visitorType << std::endl;
return;
}
std::cout << "\n🔧 批量处理所有文档使用访问者: " << visitorType << std::endl;
for (size_t i = 0; i < documents_.size(); ++i) {
std::cout << "\n--- 处理文档 #" << i << " ---" << std::endl;
documents_[i].accept(it->second.get());
}
}
void showSystemStatus() const {
std::cout << "\n📊 系统状态" << std::endl;
std::cout << "==========" << std::endl;
std::cout << "文档数量: " << documents_.size() << std::endl;
std::cout << "可用访问者: " << visitorRegistry_.size() << " 个" << std::endl;
for (const auto& pair : visitorRegistry_) {
std::cout << " • " << pair.first << ": " << pair.second->getDescription() << std::endl;
}
}
void registerCustomVisitor(const std::string& name, std::unique_ptr<DocumentVisitor> visitor) {
visitorRegistry_[name] = std::move(visitor);
std::cout << "🆕 注册自定义访问者: " << name << std::endl;
}
void runDemo() {
std::cout << "\n🎮 运行文档处理系统演示..." << std::endl;
std::cout << "========================" << std::endl;
// 创建示例文档
createDocument("技术报告");
createDocument("用户手册");
createDocument("项目提案");
// 为文档添加内容
setupDemoDocuments();
// 显示系统状态
showSystemStatus();
// 执行各种处理
processDocument(0, "stats");
processDocument(1, "spellcheck");
batchProcess("render");
std::cout << "\n✅ 文档处理系统演示完成" << std::endl;
}
private:
void setupDemoDocuments() {
// 设置第一个文档(技术报告)
Document& techReport = getDocument(0);
techReport.addElement(std::make_shared<TextElement>("intro", "本技术报告介绍了系统的设计和实现。"));
techReport.addElement(std::make_shared<TableElement>("data_table", 5, 4, "性能数据"));
techReport.addElement(std::make_shared<ImageElement>("arch_diagram", "architecture.png", 800, 600, 2.1));
// 设置第二个文档(用户手册)
Document& userManual = getDocument(1);
userManual.addElement(std::make_shared<TextElement>("welcome", "欢迎使用本系统用户手册。"));
auto usageSection = std::make_shared<ParagraphElement>("usage_section", "left");
usageSection->addChild(std::make_shared<TextElement>("step1", "第一步:启动应用程序。"));
usageSection->addChild(std::make_shared<TextElement>("step2", "第二步:配置系统参数。"));
usageSection->addChild(std::make_shared<ImageElement>("config_screen", "config.png", 600, 400, 1.5));
userManual.addElement(usageSection);
// 设置第三个文档(项目提案)
Document& proposal = getDocument(2);
proposal.addElement(std::make_shared<TextElement>("exec_summary", "本项目提案旨在开发新一代文档处理系统。"));
proposal.addElement(std::make_shared<TableElement>("budget", 4, 3, "项目预算"));
proposal.addElement(std::make_shared<TextElement>("timeline", "项目计划在六个月内完成。"));
std::cout << "✅ 设置演示文档内容完成" << std::endl;
}
};
// 高级应用:条件访问者
class ConditionalVisitor : public DocumentVisitor {
private:
std::function<bool(DocumentElement*)> condition_;
std::unique_ptr<DocumentVisitor> trueVisitor_;
std::unique_ptr<DocumentVisitor> falseVisitor_;
int trueCount_;
int falseCount_;
public:
ConditionalVisitor(std::function<bool(DocumentElement*)> condition,
std::unique_ptr<DocumentVisitor> trueVisitor,
std::unique_ptr<DocumentVisitor> falseVisitor)
: condition_(condition), trueVisitor_(std::move(trueVisitor)),
falseVisitor_(std::move(falseVisitor)), trueCount_(0), falseCount_(0) {
std::cout << "🎭 创建条件访问者" << std::endl;
}
void visit(TextElement* element) override {
processElement(element);
}
void visit(ImageElement* element) override {
processElement(element);
}
void visit(TableElement* element) override {
processElement(element);
}
void visit(ParagraphElement* element) override {
processElement(element);
}
std::string getVisitorName() const override {
return "条件访问者[" + trueVisitor_->getVisitorName() + "|" + falseVisitor_->getVisitorName() + "]";
}
std::string getDescription() const override {
return "基于条件选择访问者的条件访问者";
}
void reset() override {
trueCount_ = falseCount_ = 0;
trueVisitor_->reset();
falseVisitor_->reset();
}
std::string getResults() const override {
std::stringstream ss;
ss << "🎭 条件访问者结果:" << std::endl;
ss << " 满足条件的元素: " << trueCount_ << " 个" << std::endl;
ss << " 不满足条件的元素: " << falseCount_ << " 个" << std::endl;
ss << "\n真分支结果:" << std::endl;
ss << trueVisitor_->getResults() << std::endl;
ss << "\n假分支结果:" << std::endl;
ss << falseVisitor_->getResults();
return ss.str();
}
private:
void processElement(DocumentElement* element) {
if (condition_(element)) {
trueCount_++;
// 动态分派到正确的visit方法
element->accept(trueVisitor_.get());
} else {
falseCount_++;
element->accept(falseVisitor_.get());
}
}
};
int main() {
std::cout << "🌈 设计模式武林大会 - 访问者模式演示 🌈" << std::endl;
std::cout << "=====================================" << std::endl;
// 测试基础访问者模式
testVisitorPattern();
// 测试复合结构
testCompositeStructure();
// 测试访问者组合
testVisitorCombination();
// 运行文档处理系统演示
std::cout << "\n=== 文档处理系统演示 ===" << std::endl;
DocumentProcessingSystem processingSystem;
processingSystem.runDemo();
// 测试条件访问者
std::cout << "\n=== 条件访问者测试 ===" << std::endl;
Document testDoc("条件访问者测试");
testDoc.addElement(std::make_shared<TextElement>("text1", "重要文本内容"));
testDoc.addElement(std::make_shared<ImageElement>("img1", "photo.jpg", 100, 100, 0.5));
testDoc.addElement(std::make_shared<TextElement>("text2", "普通文本"));
auto conditionalVisitor = std::make_unique<ConditionalVisitor>(
[](DocumentElement* elem) { return elem->getElementType() == "Text"; },
std::make_unique<DocumentStatsVisitor>(),
std::make_unique<DocumentRenderVisitor>()
);
testDoc.accept(conditionalVisitor.get());
std::cout << "\n🎉 访问者模式演示全部完成!" << std::endl;
return 0;
}
访问者模式的武学心得
适用场景
- 复杂的对象结构:当一个对象结构包含很多类对象,它们有不同的接口,而你想对这些对象实施一些依赖于其具体类的操作时
- 需要很多操作:需要对一个对象结构中的对象进行很多不同且不相关的操作,而你想避免让这些操作"污染"这些对象的类时
- 定义操作接口:定义对象结构的类很少改变,但经常需要在此结构上定义新的操作时
- 累积状态:需要在遍历对象结构时累积状态时
优点
- 开闭原则:可以引入新的访问者而不必修改现有元素类
- 单一职责原则:可将同一行为的不同版本移到同一个类中
- 累积状态:访问者可以在访问各个元素时累积状态
- 灵活性:将相关的操作集中在一个访问者对象中
缺点
- 增加新元素困难:每增加一个新的元素类,都要在每一个访问者类中增加相应的具体操作
- 破坏封装:访问者模式要求访问者对象访问并调用元素对象的操作,这可能会破坏元素的封装性
- 依赖具体类:访问者模式依赖具体类,而不是抽象类
武林高手的点评
Iterator 赞叹道:"Visitor 兄的操作分离确实精妙!能够如此优雅地在不修改对象结构的情况下添加新操作,这在需要处理复杂对象结构的系统中确实无人能及。"
Composite 也点头称赞:"Visitor 兄专注于对元素的操作,而我更关注元素的结构组织。我们经常一起合作,处理复杂的层次结构。"
Visitor 谦虚回应:"诸位过奖了。每个模式都有其适用场景。在需要对复杂对象结构执行多种操作时,我的访问者模式确实能发挥重要作用。但在需要统一遍历接口时,Iterator 兄的方法更加合适。"
下章预告
在Visitor展示完他那精妙的访问艺术后,Mediator 在人群中来回穿梭、忙于调停的和事佬走出。
"Visitor 兄的操作分离确实精妙,但在对象间通信复杂、相互依赖时,需要更加中心化的协调方式。" Mediator 忙碌地说道,"下一章,我将展示如何通过中介者模式来封装对象间的交互,使它们不必直接相互引用,从而使其耦合松散!"
架构老人满意地点头:"善!对象间的协调通信确实是构建松耦合系统的关键。下一章,就请 Mediator 展示他的中介艺术!"
欲知 Mediator 如何通过中介者模式实现对象间的松耦合通信,且听下回分解!