C++ 访问者模式
介绍
访问者模式是一种行为型设计模式,它允许你在不修改类的情况下为类层次结构添加新的操作。这种模式的核心思想是将数据结构与操作分离,实现了"开放-封闭原则"(对扩展开放,对修改封闭)。
当你有一个稳定的对象结构,但经常需要添加新的操作时,访问者模式特别有用。它能够让你在不改变这些对象的类的前提下定义新的操作,这些操作会"访问"这些对象。
备注
访问者模式是C++中较为高级的设计模式之一,需要对面向对象编程和多态有较好的理解。
访问者模式的基本结构
访问者模式由以下几个关键组件组成:
- Visitor(访问者):声明了一组访问方法,每个方法对应于一种可访问的具体元素类型。
- ConcreteVisitor(具体访问者):实现了访问者接口中声明的方法,每个方法都定义了对一种具体元素的操作。
- Element(元素):定义了一个accept方法,接受访问者对象作为参数。
- ConcreteElement(具体元素):实现了accept方法,通常会调用访问者对应的访问方法。
访问者模式的实现
下面是一个简单的C++访问者模式实现:
#include <iostream>
#include <vector>
#include <memory>
// 前向声明
class Circle;
class Square;
class Rectangle;
// 访问者接口
class Visitor {
public:
virtual void visit(Circle* circle) = 0;
virtual void visit(Square* square) = 0;
virtual void visit(Rectangle* rectangle) = 0;
virtual ~Visitor() {}
};
// 元素接口
class Shape {
public:
virtual void accept(Visitor* visitor) = 0;
virtual ~Shape() {}
};
// 具体元素类
class Circle : public Shape {
public:
Circle(double radius) : radius_(radius) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getRadius() const {
return radius_;
}
private:
double radius_;
};
class Square : public Shape {
public:
Square(double side) : side_(side) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getSide() const {
return side_;
}
private:
double side_;
};
class Rectangle : public Shape {
public:
Rectangle(double width, double height) : width_(width), height_(height) {}
void accept(Visitor* visitor) override {
visitor->visit(this);
}
double getWidth() const {
return width_;
}
double getHeight() const {
return height_;
}
private:
double width_;
double height_;
};
// 具体访问者类
class AreaCalculator : public Visitor {
public:
void visit(Circle* circle) override {
double area = 3.14159 * circle->getRadius() * circle->getRadius();
std::cout << "Circle area: " << area << std::endl;
}
void visit(Square* square) override {
double area = square->getSide() * square->getSide();
std::cout << "Square area: " << area << std::endl;
}
void visit(Rectangle* rectangle) override {
double area = rectangle->getWidth() * rectangle->getHeight();
std::cout << "Rectangle area: " << area << std::endl;
}
};
class PerimeterCalculator : public Visitor {
public:
void visit(Circle* circle) override {
double perimeter = 2 * 3.14159 * circle->getRadius();
std::cout << "Circle perimeter: " << perimeter << std::endl;
}
void visit(Square* square) override {
double perimeter = 4 * square->getSide();
std::cout << "Square perimeter: " << perimeter << std::endl;
}
void visit(Rectangle* rectangle) override {
double perimeter = 2 * (rectangle->getWidth() + rectangle->getHeight());
std::cout << "Rectangle perimeter: " << perimeter << std::endl;
}
};
int main() {
std::vector<std::unique_ptr<Shape>> shapes;
shapes.push_back(std::make_unique<Circle>(5.0));
shapes.push_back(std::make_unique<Square>(4.0));
shapes.push_back(std::make_unique<Rectangle>(3.0, 5.0));
AreaCalculator areaCalc;
PerimeterCalculator perimeterCalc;
// 计算所有形状的面积
std::cout << "Areas of shapes:" << std::endl;
for (const auto& shape : shapes) {
shape->accept(&areaCalc);
}
std::cout << "\nPerimeters of shapes:" << std::endl;
// 计算所有形状的周长
for (const auto& shape : shapes) {
shape->accept(&perimeterCalc);
}
return 0;
}
程序输出:
Areas of shapes:
Circle area: 78.5398
Square area: 16
Rectangle area: 15
Perimeters of shapes:
Circle perimeter: 31.4159
Square perimeter: 16
Rectangle perimeter: 16
双分派(Double Dispatch)
访问者模式使用了一种称为"双分派"的技术,它是访问者模式的核心。双分派是指对方法的选择取决于两个对象的类型:
- 被访问元素的类型(通过虚函数accept)
- 访问者的类型(通过虚函数visit)
这种机制允许我们根据两个对象的具体类型调用正确的方法,而不需要使用大量的类型检查或条件分支。
提示
双分派是访问者模式的关键,它利用了C++的虚函数机制,使得程序能够根据元素和访问者的具体类型执行相应的操作。
访问者模式的应用场景
访问者模式在以下场景中特别有用:
-
当对象结构相对稳定,但需要经常添加新的操作:例如,一个文档对象模型(DOM)树的结构确定,但可能需要对其执行不同的操作,如格式化、验证或转换。
-
当操作需要在不相关的类上执行:例如,一组不同的图形对象需要被绘制、移动或调整大小。
-
当操作需要访问对象的内部状态:访问者可以访问元素类的内部状态,但又不破坏其封装性。
-
当需要在一组对象上执行一组不同的操作:例如,在一个编译器中,语法树的结构是固定的,但可能需要执行多种不同的操作,如代码生成、类型检查、优化等。