设计模式25-访问器模式
访问者模式的动机
- 在软件构建过程中,由于需求的改变,某些类层次结构中常常需要增加新的行为方法。此时如果直接在基类中做这样的更改,将会给此类带来繁重的变更负担,甚至破坏原有设计。
- 那么如何在不更改类层次结构的前提下,定义时根据需要的透明的为内层次结构上的各个类动态添加新的操作。从而避免上述问题。
- 当需要对一个对象结构中的各个元素进行不同的操作,而这些操作可能会频繁改变时,通常我们会将这些操作直接放在对象类的内部。然而,这样的设计违反了单一职责原则,因为对象类不仅需要管理自身的数据,还要处理不同的操作逻辑。更糟糕的是,如果需要添加新的操作,则必须修改对象类,这会导致类结构的频繁变化和难以维护。
- 访问者模式通过将数据结构与操作解耦,使得我们可以在不修改数据结构的情况下增加新的操作。访问者模式允许你将操作封装在访问者对象中,将对结构元素的操作与数据结构本身分开,这样可以增加新的操作而不改变数据结构。
定义与结构
定义
访问者模式(Visitor Pattern)是一种行为设计模式,它将对数据结构中的元素进行操作的功能封装到访问者对象中,使得在不改变数据结构的前提下增加新的操作变得容易。
结构
根据您提供的图片理解信息和图片中的文字文本,这是一张关于访问者模式(Visitor Pattern)的结构图。我将详细解释这张图的内容。
访问者模式概述
访问者模式是一种将算法与对象结构分离的设计模式,它定义了一组操作,可以在不修改对象结构的前提下为对象结构中每个元素添加新的操作。这种模式通常用于对象结构中的元素需要被不同方式处理的情况。
结构图解析
-
Client(客户端):
- 客户端是访问者模式的使用者,它负责创建并配置对象结构以及访问者对象,然后让访问者对象访问并处理结构中的元素。
-
Visitor(访问者):
- 这是一个抽象访问者类,它定义了一个或多个访问操作,这些操作将用于访问对象结构中的元素。具体访问者(如
ConcreteVisitor1
和ConcreteVisitor2
)将实现这些方法,以定义对元素的具体访问操作。
- 这是一个抽象访问者类,它定义了一个或多个访问操作,这些操作将用于访问对象结构中的元素。具体访问者(如
-
ConcreteVisitor1 和 ConcreteVisitor2:
- 这两个是具体访问者类,它们实现了
Visitor
接口中定义的访问操作。每个具体访问者都包含了访问特定元素(如ConcreteElementA
和ConcreteElementB
)的方法实现,这些方法的具体行为可以不同。
- 这两个是具体访问者类,它们实现了
-
Element(元素):
- 这是一个抽象元素类,它声明了一个接受访问者对象的
accept
方法。这个方法的目的是让访问者能够访问到元素本身,并对元素执行操作。
- 这是一个抽象元素类,它声明了一个接受访问者对象的
-
ConcreteElementA 和 ConcreteElementB:
- 这两个是具体元素类,它们实现了
Element
接口。每个具体元素类都包含了与自身相关的数据和操作,以及accept
方法的实现,该方法允许访问者访问元素的数据并对其进行处理。
- 这两个是具体元素类,它们实现了
-
accept(Visitor v):
- 这是
Element
接口和所有具体元素类中都有的方法。它接受一个访问者对象作为参数,并调用访问者的访问方法,从而允许访问者对元素进行操作。注意,图片中的文字可能有些混淆,因为accept
方法的参数是Visitor
类型,但示例中的Accept(Visitor v)
可能是想要表达这个含义。
- 这是
-
VisitConcreteElementA(ConcreteElementA) 和 VisitConcreteElementB(ConcreteElementB):
- 这些方法名可能是为了说明客户端如何调用访问者来访问特定元素,但在标准的访问者模式中,客户端通常不会直接调用这样的方法。相反,客户端会调用元素的
accept
方法,并将访问者对象作为参数传递,由accept
方法内部调用访问者的相应方法。
- 这些方法名可能是为了说明客户端如何调用访问者来访问特定元素,但在标准的访问者模式中,客户端通常不会直接调用这样的方法。相反,客户端会调用元素的
-
OperationA() 和 OperationB():
- 这些可能是
ConcreteVisitor1
和ConcreteVisitor2
类中定义的具体操作方法,用于对元素执行具体的访问操作。图片中的文本并未直接展示这些方法属于哪个类,但我们可以推断它们属于具体访问者类。
- 这些可能是
总结
这张图展示了访问者模式的主要组成部分:抽象访问者、具体访问者、抽象元素和具体元素。客户端通过创建并配置对象结构以及访问者对象,然后让访问者访问并处理结构中的元素,从而实现算法与对象结构的分离。需要注意的是,图片中的某些文本(如"Client"和"VisitConcreteElementA(ConcreteElementA)"的重复)可能存在拼写或表示上的混淆。
C++代码推导
原理代码
cpp
#include <iostream>
using namespace std;
class Visitor;
class Element
{
public:
virtual void accept(Visitor& visitor) = 0; //第一次多态辨析
virtual ~Element(){}
};
class ElementA : public Element
{
public:
void accept(Visitor &visitor) override {
visitor.visitElementA(*this);
}
};
class ElementB : public Element
{
public:
void accept(Visitor &visitor) override {
visitor.visitElementB(*this); //第二次多态辨析
}
};
class Visitor{
public:
virtual void visitElementA(ElementA& element) = 0;
virtual void visitElementB(ElementB& element) = 0;
virtual ~Visitor(){}
};
//==================================
//扩展1
class Visitor1 : public Visitor{
public:
void visitElementA(ElementA& element) override{
cout << "Visitor1 is processing ElementA" << endl;
}
void visitElementB(ElementB& element) override{
cout << "Visitor1 is processing ElementB" << endl;
}
};
//扩展2
class Visitor2 : public Visitor{
public:
void visitElementA(ElementA& element) override{
cout << "Visitor2 is processing ElementA" << endl;
}
void visitElementB(ElementB& element) override{
cout << "Visitor2 is processing ElementB" << endl;
}
};
int main()
{
Visitor2 visitor;
ElementB elementB;
elementB.accept(visitor);// double dispatch
ElementA elementA;
elementA.accept(visitor);
return 0;
}
实例代码
以下是一个使用访问者模式的C++代码示例。假设我们有一组不同类型的图形元素(例如Circle
和Rectangle
),并希望对这些元素进行不同的操作(例如计算面积和绘制)。
cpp
#include <iostream>
#include <vector>
#include <memory>
// 声明访问者接口
class Circle;
class Rectangle;
class Visitor {
public:
virtual void visit(Circle& circle) = 0;
virtual void visit(Rectangle& rectangle) = 0;
};
// 声明元素接口
class Element {
public:
virtual ~Element() = default;
virtual void accept(Visitor& visitor) = 0;
};
// 具体元素类:圆形
class Circle : public Element {
public:
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
double getRadius() const {
return radius_;
}
private:
double radius_ = 5.0;
};
// 具体元素类:矩形
class Rectangle : public Element {
public:
void accept(Visitor& visitor) override {
visitor.visit(*this);
}
double getWidth() const {
return width_;
}
double getHeight() const {
return height_;
}
private:
double width_ = 4.0;
double height_ = 6.0;
};
// 具体访问者类:计算面积
class AreaVisitor : 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(Rectangle& rectangle) override {
double area = rectangle.getWidth() * rectangle.getHeight();
std::cout << "Rectangle Area: " << area << std::endl;
}
};
// 具体访问者类:绘制图形
class DrawVisitor : public Visitor {
public:
void visit(Circle& circle) override {
std::cout << "Drawing Circle with radius " << circle.getRadius() << std::endl;
}
void visit(Rectangle& rectangle) override {
std::cout << "Drawing Rectangle with width " << rectangle.getWidth() << " and height " << rectangle.getHeight() << std::endl;
}
};
int main() {
std::vector<std::shared_ptr<Element>> elements;
elements.push_back(std::make_shared<Circle>());
elements.push_back(std::make_shared<Rectangle>());
AreaVisitor areaVisitor;
DrawVisitor drawVisitor;
// 遍历所有元素并进行计算面积操作
for (auto& element : elements) {
element->accept(areaVisitor);
}
// 遍历所有元素并进行绘制操作
for (auto& element : elements) {
element->accept(drawVisitor);
}
return 0;
}
优缺点
优点:
- 遵循单一职责原则:将具体操作从数据结构中分离,避免数据结构的修改。
- 增加新操作容易:可以通过增加新的访问者类来添加新的操作,而无需修改现有的数据结构。
- 简化复杂操作:使得复杂操作的代码组织更加清晰,访问者模式将相关操作集中到一个访问者中。
缺点:
- 违反开闭原则:虽然可以增加新的操作,但如果要添加新的数据结构类型,需要修改所有访问者的代码。
- 对象结构的公开:访问者模式要求所有元素都必须公开自己,使得访问者可以访问其内部数据,这可能破坏封装性。
- 依赖具体的元素接口:访问者模式要求元素必须有接受访问者的接口,因此在动态类型系统中应用不太方便。
应用
- 编译器:编译器使用访问者模式来遍历语法树并执行语法分析、优化、代码生成等操作。
- 对象结构稳定且操作频繁变化的系统:在对象结构较为稳定,但操作经常变化的系统中,例如图形绘制系统、报表生成系统。
- 跨不同类的操作:需要跨越多个类对其进行不同的操作时,如文件系统的遍历和操作(查找、统计、复制等)。
访问者模式在需要处理复杂结构并且操作可能频繁变化的场景中非常有用。通过将操作封装在访问者对象中,它提供了对系统进行扩展的灵活性,同时保持了代码的清晰性和可维护性。
总结
- 访问器模式通过所谓双重分发来实现在不更改(不添加新的操作)Element(元素)类层次结构的前提下,在运行时透明的微类层次结构上的各个类动态添加新的操作。
- 所谓双重分发就是访问器模式中间包括了两个多态分发,第一个为accept方法的多态辨析。第二个为VisitConcreteElementX方法的多态辨析.
- 访问器模式的最大缺点在于扩展类层次(既添即添加新的元素子类会导致访问器类的改变,因此访问性模式适用于元素类层次结构稳定的状态。而其中的操作却经常面临频繁改动。