设计模式——访问者模式
定义与概念
- 访问者模式(Visitor Pattern)是一种行为设计模式。它表示一个作用于某对象结构中的各元素的操作。它允许你在不改变这些元素的类的前提下定义作用于这些元素的新操作。
- 例如,考虑一个包含多种图形(如圆形、矩形)的绘图系统。如果要对这些图形进行多种操作(如计算面积、绘制轮廓等),可以使用访问者模式。不用在每个图形类中添加各种操作方法,而是将操作封装成独立的访问者类,让这些访问者类去访问图形对象并执行相应的操作。
结构组成
- 抽象访问者(Visitor)类:
它为每个具体元素类声明了一个访问操作。这些访问操作的名字和参数类型都与具体元素类相对应。例如,在绘图系统中,抽象访问者类可能有visitCircle(Circle* circle)和visitRectangle(Rectangle* rectangle)等方法,用-于访问圆形和矩形元素。 - 具体访问者(Concrete Visitor)类:
实现了抽象访问者类中定义的访问操作。每个具体访问者类实现的操作可能不同。比如,有一个AreaCalculatorVisitor具体访问者类,它的visitCircle(Circle* circle)方法会计算圆的面积,visitRectangle(Rectangle* rectangle)方法会计算矩形的面积;还有一个OutlineDrawerVisitor具体访问者类,它的visitCircle(Circle* circle)方法会绘制圆的轮廓,visitRectangle(Rectangle* rectangle)方法会绘制矩形的轮廓。 - 抽象元素(Element)类:
它定义了一个接受访问者的方法,通常是accept(Visitor* visitor)。这个方法以一个访问者对象作为参数,在具体元素类的实现中,会调用访问者对象对应的访问方法。例如,在图形系统中,圆形和矩形类都继承自抽象元素类,它们都需要实现accept(Visitor* visitor)方法。 - 具体元素(Concrete Element)类:
继承自抽象元素类,实现了accept(Visitor* visitor)方法。在这个方法中,将自身(this)作为参数调用访问者对象中对应的访问方法。例如,圆形类Circle的accept(Visitor* visitor)方法可能是visitor->visitCircle(this),这样就把当前圆形对象传递给访问者,让访问者对其进行操作。 - 对象结构(Object Structure)类:
它包含了可以被访问的元素集合,并且提供了遍历这些元素的方法。在绘图系统中,对象结构类可能是一个Drawing类,它包含了一个图形列表(如std::vector<Shape*>,Shape是抽象元素类),并且有一个方法可以遍历这个列表,让访问者访问每个图形。
工作原理
- 首先,客户端代码创建一个对象结构对象(如绘图系统中的Drawing对象),并向其中添加具体元素对象(如圆形和矩形对象)。然后,创建一个具体访问者对象(如AreaCalculatorVisitor)。接着,通过对象结构对象的遍历方法,让每个元素接受这个访问者。在元素接受访问者时,会调用访问者对象中对应的访问方法,从而实现对元素的特定操作。
- 例如,在一个电商系统中有多种商品(如电子产品、服装产品),抽象元素类是Product,具体元素类是ElectronicProduct和ClothingProduct。对象结构类是ProductList,包含了商品列表。抽象访问者类是ProductVisitor,有visitElectronicProduct(ElectronicProduct* product)和visitClothingProduct(ClothingProduct* product)等方法。具体访问者类可以是PriceCalculatorVisitor,用于计算不同商品的价格。当需要计算商品价格时,先创建ProductList对象并添加商品,然后创建PriceCalculatorVisitor对象,通过ProductList的遍历方法让每个商品接受访问者,访问者就可以计算出商品价格。
代码示例
以下是一个简单的图形系统访问者模式示例。
首先是抽象访问者类:
class ShapeVisitor {
public:
virtual void visitCircle(Circle* circle) = 0;
virtual void visitRectangle(Rectangle* rectangle) = 0;
};
- 具体访问者类 - 计算面积访问者:
class AreaCalculator : public ShapeVisitor {
public:
void visitCircle(Circle* circle) override {
double area = 3.14159 * circle->radius * circle->radius;
std::cout << "圆的面积是: " << area << std::endl;
}
void visitRectangle(Rectangle* rectangle) override {
double area = rectangle->width * rectangle->height;
std::cout << "矩形的面积是: " << area << std::endl;
}
};
- 具体访问者类 - 绘制轮廓访问者:
class OutlineDrawer : public ShapeVisitor {
public:
void visitCircle(Circle* circle) override {
std::cout << "绘制圆的轮廓。" << std::endl;
}
void visitRectangle(Rectangle* rectangle) override {
std::cout << "绘制矩形的轮廓。" << std::endl;
}
};
- 抽象元素类:
class Shape {
public:
virtual void accept(ShapeVisitor* visitor) = 0;
};
- 具体元素类 - 圆形:
class Circle : public Shape {
public:
double radius;
Circle(double r) : radius(r) {}
void accept(ShapeVisitor* visitor) override {
visitor->visitCircle(this);
}
};
- 具体元素类 - 矩形:
class Rectangle : public Shape {
public:
double width;
double height;
Rectangle(double w, double h) : width(w), height(h) {}
void accept(ShapeVisitor* visitor) override {
visitor->visitRectangle(this);
}
};
- 对象结构类 - 绘图:
class Drawing {
private:
std::vector<Shape*> shapes;
public:
void addShape(Shape* shape) {
shapes.push_back(shape);
}
void accept(ShapeVisitor* visitor) {
for (Shape* shape : shapes) {
shape->accept(visitor);
}
}
};
使用示例:
int main() {
Drawing drawing;
Circle circle(5.0);
Rectangle rectangle(4.0, 6.0);
drawing.addShape(&circle);
drawing.addShape(&rectangle);
AreaCalculator areaCalculator;
drawing.accept(&areaCalculator);
OutlineDrawer outlineDrawer;
drawing.accept(&outlineDrawer);
return 0;
}
优点
- 增加新操作容易:
如果要对元素集合添加新的操作,只需要创建一个新的具体访问者类,实现相应的访问方法即可。在图形系统中,如果要添加一个计算图形周长的操作,只需要创建一个新的访问者类,如PerimeterCalculatorVisitor,实现计算圆形周长和矩形周长的方法,而不需要修改图形类本身。 - 符合开闭原则:
可以在不修改元素类的情况下,通过创建新的访问者类来扩展功能。例如,在一个文档管理系统中,有多种文档元素(如文本段落、图片),如果要添加一个新的文档格式转换操作,只需要创建一个新的访问者类,而不用修改文本段落和图片等元素类的代码。 - 分离数据结构和操作逻辑:
元素类主要负责存储数据结构相关的内容,而访问者类负责操作逻辑。这样使得代码结构更加清晰,便于维护和理解。在一个复杂的软件系统中,如企业资源规划(ERP)系统,通过访问者模式可以将业务数据(如订单、库存等元素)和业务操作(如统计订单金额、更新库存等访问者)分离。
缺点
- 增加新元素困难:
当需要添加新的元素类时,需要修改抽象访问者类和所有的具体访问者类,添加新的访问方法来适应新元素。例如,在图形系统中,如果要添加一个新的图形类型(如三角形),需要在抽象访问者类中添加visitTriangle(Triangle* triangle)方法,并且在所有的具体访问者类中实现这个新方法。 - 违反了依赖倒置原则:
访问者类依赖于具体元素类,而不是依赖于抽象元素类。这可能导致代码的可维护性和灵活性在一定程度上下降。例如,在一个复杂的系统中,如果元素类的结构发生变化,可能需要大量修改访问者类的代码。
原文地址:https://blog.csdn.net/chuliling0446/article/details/144006860
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!