1. 模式定义与核心思想
访问者模式表示一个作用于某对象结构中的各元素的操作。它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作。
核心思想:
双重分发 (Double Dispatch):通过两次多态调用来实现操作的选择。第一次选择访问者类型,第二次选择元素类型。
分离关注点:将数据结构与数据操作分离,将相关的操作集中到一个访问者对象中。
开闭原则:容易添加新的操作(新的访问者),但难以添加新的元素类型。
2. 模式结构(角色分析)
访问者模式包含五个主要角色:
Visitor (访问者接口)
为每个 ConcreteElement 类声明一个 visit 操作。
ConcreteVisitor (具体访问者)
实现每个由 Visitor 声明的操作。
每个操作实现算法的一部分,而该算法片段是对应于结构中对象的类。
Element (元素接口)
定义一个 accept 方法,以一个访问者作为参数。
ConcreteElement (具体元素)
实现 accept 方法,通常都是:visitor.visit(this)
ObjectStructure (对象结构)
能枚举它的元素。
可以提供一个高层的接口以允许访问者访问它的元素。
3. 经典C++实现示例
示例 1:几何图形处理(经典示例)
展示如何对不同的几何图形执行不同的操作。
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
// 前向声明
class Circle;
class Rectangle;
class Triangle;
// Visitor: 图形访问者接口
class ShapeVisitor {
public:
virtual ~ShapeVisitor() = default;
virtual void visit(Circle& circle) = 0;
virtual void visit(Rectangle& rectangle) = 0;
virtual void visit(Triangle& triangle) = 0;
};
// Element: 图形接口
class Shape {
public:
virtual ~Shape() = default;
virtual void accept(ShapeVisitor& visitor) = 0;
virtual std::string getName() const = 0;
};
// ConcreteElement: 圆形
class Circle : public Shape {
private:
double radius_;
public:
Circle(double radius) : radius_(radius) {}
void accept(ShapeVisitor& visitor) override {
visitor.visit(*this);
}
std::string getName() const override { return "Circle"; }
double getRadius() const { return radius_; }
double getArea() const { return M_PI * radius_ * radius_; }
double getPerimeter() const { return 2 * M_PI * radius_; }
};
// ConcreteElement: 矩形
class Rectangle : public Shape {
private:
double width_, height_;
public:
Rectangle(double width, double height) : width_(width), height_(height) {}
void accept(ShapeVisitor& visitor) override {
visitor.visit(*this);
}
std::string getName() const override { return "Rectangle"; }
double getWidth() const { return width_; }
double getHeight() const { return height_; }
double getArea() const { return width_ * height_; }
double getPerimeter() const { return 2 * (width_ + height_); }
};
// ConcreteElement: 三角形
class Triangle : public Shape {
private:
double a_, b_, c_;
public:
Triangle(double a, double b, double c) : a_(a), b_(b), c_(c) {}
void accept(ShapeVisitor& visitor) override {
visitor.visit(*this);
}
std::string getName() const override { return "Triangle"; }
double getA() const { return a_; }
double getB() const { return b_; }
double getC() const { return c_; }
double getArea() const {
double s = (a_ + b_ + c_) / 2;
return std::sqrt(s * (s - a_) * (s - b_) * (s - c_));
}
double getPerimeter() const { return a_ + b_ + c_; }
};
// ConcreteVisitor: 面积计算器
class AreaCalculator : public ShapeVisitor {
private:
double totalArea_ = 0.0;
public:
void visit(Circle& circle) override {
double area = circle.getArea();
std::cout << "Circle area: " << area << "\n";
totalArea_ += area;
}
void visit(Rectangle& rectangle) override {
double area = rectangle.getArea();
std::cout << "Rectangle area: " << area << "\n";
totalArea_ += area;
}
void visit(Triangle& triangle) override {
double area = triangle.getArea();
std::cout << "Triangle area: " << area << "\n";
totalArea_ += area;
}
double getTotalArea() const { return totalArea_; }
void reset() { totalArea_ = 0.0; }
};
// ConcreteVisitor: 周长计算器
class PerimeterCalculator : public ShapeVisitor {
private:
double totalPerimeter_ = 0.0;
public:
void visit(Circle& circle) override {
double perimeter = circle.getPerimeter();
std::cout << "Circle perimeter: " << perimeter << "\n";
totalPerimeter_ += perimeter;
}
void visit(Rectangle& rectangle) override {
double perimeter = rectangle.getPerimeter();
std::cout << "Rectangle perimeter: " << perimeter << "\n";
totalPerimeter_ += perimeter;
}
void visit(Triangle& triangle) override {
double perimeter = triangle.getPerimeter();
std::cout << "Triangle perimeter: " << perimeter << "\n";
totalPerimeter_ += perimeter;
}
double getTotalPerimeter() const { return totalPerimeter_; }
void reset() { totalPerimeter_ = 0.0; }
};
// ConcreteVisitor: 图形信息输出器
class ShapePrinter : public ShapeVisitor {
public:
void visit(Circle& circle) override {
std::cout << "Circle: radius = " << circle.getRadius()
<< ", area = " << circle.getArea() << "\n";
}
void visit(Rectangle& rectangle) override {
std::cout << "Rectangle: " << rectangle.getWidth() << " x " << rectangle.getHeight()
<< ", area = " << rectangle.getArea() << "\n";
}
void visit(Triangle& triangle) override {
std::cout << "Triangle: sides = " << triangle.getA() << ", "
<< triangle.getB() << ", " << triangle.getC()
<< ", area = " << triangle.getArea() << "\n";
}
};
// ObjectStructure: 图形集合
class ShapeCollection {
private:
std::vector<std::unique_ptr<Shape>> shapes_;
public:
void addShape(std::unique_ptr<Shape> shape) {
shapes_.push_back(std::move(shape));
}
void accept(ShapeVisitor& visitor) {
for (auto& shape : shapes_) {
shape->accept(visitor);
}
}
size_t size() const { return shapes_.size(); }
};
// 客户端代码
int main() {
ShapeCollection collection;
// 添加各种图形
collection.addShape(std::make_unique<Circle>(5.0));
collection.addShape(std::make_unique<Rectangle>(4.0, 6.0));
collection.addShape(std::make_unique<Triangle>(3.0, 4.0, 5.0));
std::cout << "=== Shape Information ===\n";
ShapePrinter printer;
collection.accept(printer);
std::cout << "\n=== Calculating Total Area ===\n";
AreaCalculator areaCalc;
collection.accept(areaCalc);
std::cout << "Total area: " << areaCalc.getTotalArea() << "\n";
std::cout << "\n=== Calculating Total Perimeter ===\n";
PerimeterCalculator perimeterCalc;
collection.accept(perimeterCalc);
std::cout << "Total perimeter: " << perimeterCalc.getTotalPerimeter() << "\n";
return 0;
}
示例 2:编译器抽象语法树(AST)处理
展示编译器中对不同语法节点的处理。
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 前向声明
class AssignmentNode;
class BinaryOpNode;
class NumberNode;
class VariableNode;
// Visitor: AST访问者接口
class ASTVisitor {
public:
virtual ~ASTVisitor() = default;
virtual void visit(AssignmentNode& node) = 0;
virtual void visit(BinaryOpNode& node) = 0;
virtual void visit(NumberNode& node) = 0;
virtual void visit(VariableNode& node) = 0;
};
// Element: AST节点接口
class ASTNode {
public:
virtual ~ASTNode() = default;
virtual void accept(ASTVisitor& visitor) = 0;
};
// ConcreteElement: 赋值语句节点
class AssignmentNode : public ASTNode {
private:
std::string varName_;
std::unique_ptr<ASTNode> value_;
public:
AssignmentNode(const std::string& varName, std::unique_ptr<ASTNode> value)
: varName_(varName), value_(std::move(value)) {}
void accept(ASTVisitor& visitor) override {
visitor.visit(*this);
}
const std::string& getVarName() const { return varName_; }
ASTNode* getValue() const { return value_.get(); }
};
// ConcreteElement: 二元操作节点
class BinaryOpNode : public ASTNode {
private:
char op_;
std::unique_ptr<ASTNode> left_;
std::unique_ptr<ASTNode> right_;
public:
BinaryOpNode(char op, std::unique_ptr<ASTNode> left, std::unique_ptr<ASTNode> right)
: op_(op), left_(std::move(left)), right_(std::move(right)) {}
void accept(ASTVisitor& visitor) override {
visitor.visit(*this);
}
char getOp() const { return op_; }
ASTNode* getLeft() const { return left_.get(); }
ASTNode* getRight() const { return right_.get(); }
};
// ConcreteElement: 数字字面量节点
class NumberNode : public ASTNode {
private:
int value_;
public:
NumberNode(int value) : value_(value) {}
void accept(ASTVisitor& visitor) override {
visitor.visit(*this);
}
int getValue() const { return value_; }
};
// ConcreteElement: 变量引用节点
class VariableNode : public ASTNode {
private:
std::string name_;
public:
VariableNode(const std::string& name) : name_(name) {}
void accept(ASTVisitor& visitor) override {
visitor.visit(*this);
}
const std::string& getName() const { return name_; }
};
// ConcreteVisitor: 代码生成器
class CodeGenerator : public ASTVisitor {
private:
int indentLevel_ = 0;
void printIndent() {
for (int i = 0; i < indentLevel_; ++i) {
std::cout << " ";
}
}
public:
void visit(AssignmentNode& node) override {
printIndent();
std::cout << node.getVarName() << " = ";
node.getValue()->accept(*this);
std::cout << ";\n";
}
void visit(BinaryOpNode& node) override {
std::cout << "(";
node.getLeft()->accept(*this);
std::cout << " " << node.getOp() << " ";
node.getRight()->accept(*this);
std::cout << ")";
}
void visit(NumberNode& node) override {
std::cout << node.getValue();
}
void visit(VariableNode& node) override {
std::cout << node.getName();
}
};
// ConcreteVisitor: 类型检查器
class TypeChecker : public ASTVisitor {
public:
void visit(AssignmentNode& node) override {
std::cout << "Type checking assignment: " << node.getVarName() << "\n";
node.getValue()->accept(*this);
}
void visit(BinaryOpNode& node) override {
std::cout << "Type checking binary operation: " << node.getOp() << "\n";
node.getLeft()->accept(*this);
node.getRight()->accept(*this);
}
void visit(NumberNode& node) override {
std::cout << "Found number literal: " << node.getValue() << " (type: int)\n";
}
void visit(VariableNode& node) override {
std::cout << "Found variable reference: " << node.getName() << " (type: unknown)\n";
}
};
// 构建AST: x = (a + 5) * 2
std::unique_ptr<ASTNode> createSampleAST() {
// (a + 5)
auto left = std::make_unique<BinaryOpNode>(
'+',
std::make_unique<VariableNode>("a"),
std::make_unique<NumberNode>(5)
);
// (a + 5) * 2
auto expr = std::make_unique<BinaryOpNode>(
'*',
std::move(left),
std::make_unique<NumberNode>(2)
);
// x = (a + 5) * 2
return std::make_unique<AssignmentNode>(
"x",
std::move(expr)
);
}
// 客户端代码
int main() {
auto ast = createSampleAST();
std::cout << "=== Code Generation ===\n";
CodeGenerator codeGen;
ast->accept(codeGen);
std::cout << "\n=== Type Checking ===\n";
TypeChecker typeChecker;
ast->accept(typeChecker);
return 0;
}
示例 3:文档导出系统
展示将文档内容导出为不同格式。
#include <iostream>
#include <vector>
#include <memory>
#include <string>
// 前向声明
class TextElement;
class ImageElement;
class TableElement;
// Visitor: 文档导出器接口
class DocumentExporter {
public:
virtual ~DocumentExporter() = default;
virtual void visit(TextElement& element) = 0;
virtual void visit(ImageElement& element) = 0;
virtual void visit(TableElement& element) = 0;
virtual std::string getResult() const = 0;
};
// Element: 文档元素接口
class DocumentElement {
public:
virtual ~DocumentElement() = default;
virtual void accept(DocumentExporter& exporter) = 0;
};
// ConcreteElement: 文本元素
class TextElement : public DocumentElement {
private:
std::string content_;
public:
TextElement(const std::string& content) : content_(content) {}
void accept(DocumentExporter& exporter) override {
exporter.visit(*this);
}
const std::string& getContent() const { return content_; }
};
// ConcreteElement: 图片元素
class ImageElement : public DocumentElement {
private:
std::string src_;
int width_, height_;
public:
ImageElement(const std::string& src, int width, int height)
: src_(src), width_(width), height_(height) {}
void accept(DocumentExporter& exporter) override {
exporter.visit(*this);
}
const std::string& getSrc() const { return src_; }
int getWidth() const { return width_; }
int getHeight() const { return height_; }
};
// ConcreteElement: 表格元素
class TableElement : public DocumentElement {
private:
std::vector<std::vector<std::string>> data_;
public:
TableElement(const std::vector<std::vector<std::string>>& data) : data_(data) {}
void accept(DocumentExporter& exporter) override {
exporter.visit(*this);
}
const std::vector<std::vector<std::string>>& getData() const { return data_; }
};
// ConcreteVisitor: HTML导出器
class HTMLExporter : public DocumentExporter {
private:
std::string result_;
public:
void visit(TextElement& element) override {
result_ += "<p>" + element.getContent() + "</p>\n";
}
void visit(ImageElement& element) override {
result_ += "<img src=\"" + element.getSrc() + "\" width=\""
+ std::to_string(element.getWidth()) + "\" height=\""
+ std::to_string(element.getHeight()) + "\">\n";
}
void visit(TableElement& element) override {
result_ += "<table border=\"1\">\n";
for (const auto& row : element.getData()) {
result_ += " <tr>\n";
for (const auto& cell : row) {
result_ += " <td>" + cell + "</td>\n";
}
result_ += " </tr>\n";
}
result_ += "</table>\n";
}
std::string getResult() const override { return result_; }
};
// ConcreteVisitor: Markdown导出器
class MarkdownExporter : public DocumentExporter {
private:
std::string result_;
public:
void visit(TextElement& element) override {
result_ += element.getContent() + "\n\n";
}
void visit(ImageElement& element) override {
result_ += " + ")\n\n";
}
void visit(TableElement& element) override {
const auto& data = element.getData();
if (data.empty()) return;
// 表头
for (size_t i = 0; i < data[0].size(); ++i) {
result_ += "| " + data[0][i] + " ";
}
result_ += "|\n";
// 分隔线
for (size_t i = 0; i < data[0].size(); ++i) {
result_ += "| --- ";
}
result_ += "|\n";
// 数据行
for (size_t i = 1; i < data.size(); ++i) {
for (const auto& cell : data[i]) {
result_ += "| " + cell + " ";
}
result_ += "|\n";
}
result_ += "\n";
}
std::string getResult() const override { return result_; }
};
// 文档对象
class Document {
private:
std::vector<std::unique_ptr<DocumentElement>> elements_;
public:
void addElement(std::unique_ptr<DocumentElement> element) {
elements_.push_back(std::move(element));
}
std::string exportDocument(DocumentExporter& exporter) {
for (auto& element : elements_) {
element->accept(exporter);
}
return exporter.getResult();
}
};
// 客户端代码
int main() {
Document doc;
// 添加文档元素
doc.addElement(std::make_unique<TextElement>("This is a sample document"));
doc.addElement(std::make_unique<ImageElement>("image.jpg", 800, 600));
std::vector<std::vector<std::string>> tableData = {
{"Name", "Age", "City"},
{"Alice", "25", "New York"},
{"Bob", "30", "London"}
};
doc.addElement(std::make_unique<TableElement>(tableData));
// 导出为HTML
HTMLExporter htmlExporter;
std::string html = doc.exportDocument(htmlExporter);
std::cout << "=== HTML Export ===\n" << html << "\n";
// 导出为Markdown
MarkdownExporter mdExporter;
std::string markdown = doc.exportDocument(mdExporter);
std::cout << "=== Markdown Export ===\n" << markdown << "\n";
return 0;
}
4. 访问者模式的优缺点
优点:
开闭原则:容易添加新的操作(新的访问者)。
单一职责原则:将相关操作集中在一个访问者中。
访问者可以累积状态:可以在遍历过程中收集信息。
算法集中化:相关的行为不是分散在各个元素类中。
缺点:
难以添加新的元素类型:每添加一个新的元素类型,都要修改所有访问者。
破坏封装性:访问者通常需要访问元素的内部细节。
元素接口依赖访问者:元素接口必须提供 accept 方法。
可能违反迪米特法则:访问者需要了解具体元素类的细节。
5. 适用场景
对象结构稳定:但需要在此结构上定义新的操作。
需要对一个对象结构中的对象进行很多不同且不相关的操作。
需要避免污染元素类:不希望这些操作"污染"这些元素的类。
编译器、解释器:AST遍历、类型检查、代码生成等。
文档处理:导出为不同格式、语法高亮等。
访问者模式通过双重分发机制,提供了强大的扩展能力,特别适合于需要对复杂对象结构执行多种不同操作的场景。
访问者模式详解与应用

1004

被折叠的 条评论
为什么被折叠?



