哈喽,各位好!今天咱们来聊聊C++中的Visitor模式,一个能让你在不改动现有类结构的前提下,给它们“穿新衣戴新帽”的神奇设计模式。说白了,就是给你的类增加新功能,但又不想动它们的老代码。
故事的开始:一个简单的图形系统
想象一下,你正在开发一个图形系统,里面有圆形(Circle)、矩形(Rectangle)和三角形(Triangle)三种基本图形。每个图形都有自己的绘制(draw)方法,用来在屏幕上显示自己。
#include <iostream>
#include <vector>
class Shape {
public:
virtual void draw() = 0;
virtual ~Shape() {} // 记得加虚析构函数,防止内存泄漏
};
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circlen";
}
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Rectanglen";
}
};
class Triangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Trianglen";
}
};
int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle());
shapes.push_back(new Rectangle());
shapes.push_back(new Triangle());
for (Shape* shape : shapes) {
shape->draw();
}
// 别忘了释放内存,好习惯!
for (Shape* shape : shapes) {
delete shape;
}
shapes.clear();
return 0;
}
现在,需求来了!老板说:“我们要能计算这些图形的面积,还要能把它们导出成SVG格式!”
直接修改?No Way!
最直接的想法就是在每个图形类里加上 calculateArea()
和 exportToSVG()
方法。
// 如果我们直接修改类...
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circlen";
}
double calculateArea() {
// 计算圆形面积
return 3.14159; // 简化
}
void exportToSVG() {
std::cout << "<circle ... />n"; // 简化
}
};
// Rectangle 和 Triangle 类似...
这样做的问题是:
- 违反开闭原则: 每次新增操作都要修改已有的类,这违反了“对扩展开放,对修改关闭”的原则。万一这些类在很多地方被使用,改动起来风险很大。
- 代码膨胀: 每个图形类都要重复实现类似的功能,代码冗余。
- 职责不单一: 图形类既负责图形本身的属性,又负责计算面积和导出格式,职责不单一,不利于维护。
Visitor 模式闪亮登场
Visitor模式就像一个“访问者”,它可以“拜访”不同的图形,并对它们执行特定的操作,而无需修改图形类本身。
核心角色:
- Visitor (访问者): 定义了访问每个
Element
(也就是我们的图形) 的接口。每个具体的操作都对应一个visit
方法。 - ConcreteVisitor (具体访问者): 实现了
Visitor
接口,对每个Element
执行具体的操作。比如,AreaCalculator
负责计算面积,SVGExporter
负责导出SVG。 - Element (元素): 定义了
accept()
方法,允许Visitor
访问自己。 - ConcreteElement (具体元素): 实现了
Element
接口,在accept()
方法中调用Visitor
相应的visit
方法,并将自己作为参数传递给Visitor
。 - ObjectStructure (对象结构): 可选角色,负责管理
Element
集合,并让Visitor
依次访问每个Element
。
代码实现:
- Shape 类(Element):
class Shape {
public:
virtual void accept(class Visitor* visitor) = 0; // 关键的 accept 方法
virtual void draw() = 0;
virtual ~Shape() {}
};
- Circle, Rectangle, Triangle 类(ConcreteElement):
class Circle : public Shape {
public:
void draw() override {
std::cout << "Drawing Circlen";
}
void accept(Visitor* visitor) override; // forward declaration needed
};
class Rectangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Rectanglen";
}
void accept(Visitor* visitor) override;
};
class Triangle : public Shape {
public:
void draw() override {
std::cout << "Drawing Trianglen";
}
void accept(Visitor* visitor) override;
};
- Visitor 接口:
class Visitor {
public:
virtual void visit(Circle* circle) = 0;
virtual void visit(Rectangle* rectangle) = 0;
virtual void visit(Triangle* triangle) = 0;
virtual ~Visitor() {}
};
- AreaCalculator 类(ConcreteVisitor):
class AreaCalculator : public Visitor {
public:
double circleArea;
double rectangleArea;
double triangleArea;
AreaCalculator() : circleArea(0.0), rectangleArea(0.0), triangleArea(0.0) {}
void visit(Circle* circle) override {
circleArea = 3.14 * 5 * 5; // 假设半径为5
}
void visit(Rectangle* rectangle) override {
rectangleArea = 10 * 5; // 假设长为10,宽为5
}
void visit(Triangle* triangle) override {
triangleArea = 0.5 * 8 * 6; // 假设底为8,高为6
}
};
- SVGExporter 类(ConcreteVisitor):
class SVGExporter : public Visitor {
public:
std::string circleSVG;
std::string rectangleSVG;
std::string triangleSVG;
void visit(Circle* circle) override {
circleSVG = "<circle cx='50' cy='50' r='5' fill='red' />"; // 简化
}
void visit(Rectangle* rectangle) override {
rectangleSVG = "<rect x='10' y='10' width='10' height='5' fill='blue' />"; // 简化
}
void visit(Triangle* triangle) override {
triangleSVG = "<polygon points='0,0 10,0 5,8' fill='green' />"; // 简化
}
};
- 实现
accept
方法:
void Circle::accept(Visitor* visitor) {
visitor->visit(this);
}
void Rectangle::accept(Visitor* visitor) {
visitor->visit(this);
}
void Triangle::accept(Visitor* visitor) {
visitor->visit(this);
}
- 使用 Visitor:
int main() {
std::vector<Shape*> shapes;
shapes.push_back(new Circle());
shapes.push_back(new Rectangle());
shapes.push_back(new Triangle());
// 计算面积
AreaCalculator areaCalculator;
for (Shape* shape : shapes) {
shape->accept(&areaCalculator);
}
std::cout << "Circle Area: " << areaCalculator.circleArea << std::endl;
std::cout << "Rectangle Area: " << areaCalculator.rectangleArea << std::endl;
std::cout << "Triangle Area: " << areaCalculator.triangleArea << std::endl;
// 导出 SVG
SVGExporter svgExporter;
for (Shape* shape : shapes) {
shape->accept(&svgExporter);
}
std::cout << "Circle SVG: " << svgExporter.circleSVG << std::endl;
std::cout << "Rectangle SVG: " << svgExporter.rectangleSVG << std::endl;
std::cout << "Triangle SVG: " << svgExporter.triangleSVG << std::endl;
// 释放内存
for (Shape* shape : shapes) {
delete shape;
}
shapes.clear();
return 0;
}
ObjectStructure 的应用 (可选)
如果你的图形集合非常复杂,或者你需要对图形进行统一管理,可以使用 ObjectStructure
。
#include <vector>
#include <algorithm>
class ObjectStructure {
private:
std::vector<Shape*> shapes;
public:
void add(Shape* shape) {
shapes.push_back(shape);
}
void remove(Shape* shape) {
// 使用 erase-remove idiom 安全移除元素
shapes.erase(std::remove(shapes.begin(), shapes.end(), shape), shapes.end());
}
void accept(Visitor* visitor) {
for (Shape* shape : shapes) {
shape->accept(visitor);
}
}
~ObjectStructure(){
for(auto shape: shapes){
delete shape;
}
shapes.clear();
}
};
int main() {
ObjectStructure shapes;
shapes.add(new Circle());
shapes.add(new Rectangle());
shapes.add(new Triangle());
// 计算面积
AreaCalculator areaCalculator;
shapes.accept(&areaCalculator);
std::cout << "Circle Area: " << areaCalculator.circleArea << std::endl;
std::cout << "Rectangle Area: " << areaCalculator.rectangleArea << std::endl;
std::cout << "Triangle Area: " << areaCalculator.triangleArea << std::endl;
return 0;
}
Visitor 模式的优点:
- 开闭原则: 可以方便地添加新的操作,而无需修改现有的类结构。
- 职责分离: 将操作与对象结构分离,使代码更清晰、更易于维护。
- 灵活性: 可以根据需要组合不同的
Visitor
,执行不同的操作。
Visitor 模式的缺点:
- 复杂性增加: 引入了额外的类和接口,增加了代码的复杂性。
- 类型依赖:
Visitor
需要知道所有ConcreteElement
的类型,如果新增Element
,需要修改Visitor
接口和所有ConcreteVisitor
。这被称为“Visitor 模式的双重分派问题”。 - 破坏封装: 如果Visitor需要访问Element的私有成员,可能会需要将这些成员设置为public或者提供friend访问,从而破坏了封装性。
何时使用 Visitor 模式:
- 需要对一个对象结构(一组对象)执行多种不同的操作,而这些操作之间没有必然联系。
- 希望避免在对象结构中添加大量重复的代码。
- 对象结构稳定,但操作可能会频繁变化。
Visitor 模式的应用场景:
- 编译器: 语法树的遍历和分析。
- 文档处理: 对文档对象模型(DOM)进行各种操作,如格式化、验证等。
- 图形编辑器: 对图形对象进行各种操作,如绘制、选择、移动等。
与其它模式的比较:
模式 | 优点 | 缺点 | 适用场景 |
---|---|---|---|
Visitor | 开闭原则,职责分离,灵活性 | 复杂性增加,类型依赖,可能破坏封装 | 需要对对象结构执行多种不同操作,对象结构稳定,操作可能频繁变化 |
Strategy | 定义算法簇,可以动态切换算法。 | 客户端需要知道所有策略类,并选择合适的策略。 | 需要动态选择算法,算法之间可以互换。 |
Command | 将请求封装成对象,可以实现请求的排队、撤销、重做等功能。 | 可能产生大量的具体命令类。 | 需要将请求封装成对象,并支持撤销、重做等功能。 |
Template Method | 定义算法的骨架,将一些步骤延迟到子类实现。 | 子类必须遵守父类的算法骨架。 | 需要定义算法的骨架,并将一些步骤延迟到子类实现。 |
Decorator | 动态地给对象添加额外的职责。 | 可能产生大量的装饰器类。 | 需要动态地给对象添加额外的职责。 |
总结
Visitor 模式是一种强大的设计模式,它可以让你在不修改现有类结构的前提下,给它们添加新的功能。虽然它有一定的复杂性,但如果使用得当,可以大大提高代码的灵活性和可维护性。记住,任何设计模式都不是银弹,要根据实际情况选择合适的模式。
希望这次的讲解能够帮助大家更好地理解 Visitor 模式。下次再见!