第二十章:遍历万象,操作随心——Visitor的访问艺术
第二十章:遍历万象,操作随心——Visitor的访问艺术
风云再起,访问学者登场
在State展示完他那精妙的状态艺术后,Visitor彬彬有礼地走出,向复杂的对象结构行礼。他的举止优雅从容,仿佛一位学识渊博的学者在审视着精密的学术体系。
"State兄的状态管理确实精妙,"Visitor优雅地说道,“但在不修改现有对象结构的前提下定义新操作方面,需要更加灵活的访问方式。诸位请看——”
Visitor的身形在几个不同的对象结构间穿梭,却始终保持着适当的距离:“我的访问者模式,专为解决算法与对象结构分离问题而生!我允许你在不修改现有对象结构的前提下,定义作用于这些元素的新操作!”
架构老人眼中闪过赞许之色:“善!Visitor,就请你为大家展示这访问艺术的精妙所在。”
访问者模式的核心要义
Visitor面向众人,开始阐述他的武学真谛:
“在我的访问者模式中,主要包含两个核心角色:”
“Visitor(访问者):为对象结构中的每个ConcreteElement类声明一个Visit操作。”
“ConcreteVisitor(具体访问者):实现每个由Visitor声明的操作。”
“Element(元素):定义一个Accept操作,它以一个访问者为参数。”
“ConcreteElement(具体元素):实现Accept操作。”
"其精妙之处在于,"Visitor继续道,“我将算法与对象结构分离,使得可以在不修改现有元素类的情况下增加新的操作。访问者可以累积状态,将有关状态存储在其内部!”
C++实战:文档处理系统
"且让我以一个文档处理系统为例,展示访问者模式的实战应用。"Visitor说着,手中凝聚出一道道代码流光。
基础框架搭建
首先,Visitor定义了文档元素和访问者接口:
#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展示了各种文档元素的具体实现:
// 具体元素:文本元素
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展示了各种文档处理操作的具体实现:
// 具体访问者:文档统计访问者
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 武功秘籍图
实战演练:高级访问系统
Visitor继续展示更复杂的访问者模式应用:
// 具体访问者:文档导出访问者
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());}}
};
完整测试代码
// 测试访问者模式
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 如何通过中介者模式实现对象间的松耦合通信,且听下回分解!