一、访问者模介绍
访问者模式(Visitor Pattern)是一种行为型设计模式,用于将操作封装在访问者对象中,以便在不改变被访问对象的类的前提下,定义新的操作。它允许你在不修改现有代码的情况下,向对象结构中添加新的操作。例如:类data具有加、减、乘、除四种行为,通过访问者模式,允许在不修改现有代码的情况下,添加新的行为:取余、取整,这两个行为在访问者中实现。
访问者模式结构图
- 抽象访问者(Visitor):定义一个访问方法,用于访问每种具体元素的对象。通常包含一个visit方法,用于访问不同的元素类型。
- 具体访问者(ConcreteVisitor):实现抽象访问者接口,提供具体的操作实现。
- 元素接口(Element):定义一个接受访问者的方法。这个方法通常叫做
accept
,它将访问者作为参数。- 具体元素(ConcreteElement):实现元素接口,并定义具体的
accept
方法,以调用访问者的相应访问方法。- 对象结构(Object Structure):持有一组元素,并可以遍历这些元素,以便访问者能访问它们。通常是一个集合类,比如列表或集合。
二、访问者模式的设计方法
有一个公园,分为A、B两个区,公园的属性不变,有小溪、稀有的树种,珍贵的鸟类。该公园存在多个访问者:清洁工A负责打扫公园的A区,清洁工B负责打扫公园的B区,公园的管理者负责检点各项事务是否完成,上级领导可以视察公园,老百姓可以在公园放松和锻炼身体。也就是说,对于同一个公园,不同的访问者有不同的行为操作,而且访问者的种类也可能需要根据时间的推移而变化(行为的扩展性)。
visitor.cpp
#include <iostream>
#include <vector>
#include <string>
class Visitor;
class ParkElement {
public:
virtual void accept(Visitor& visitor) = 0;
virtual ~ParkElement() = default;
};
class ASection : public ParkElement {
public:
void accept(Visitor& visitor) override;
void cleanA() const {
std::cout << "我在打扫公园A区树叶 " << std::endl;
}
};
class BSection : public ParkElement {
public:
void accept(Visitor& visitor) override;
void cleanB() const {
std::cout << "我在打扫公园B区树叶 " << std::endl;
}
};
class Visitor {
public:
virtual void visitASection(ASection& A) = 0;
virtual void visitBSection(BSection& B) = 0;
virtual ~Visitor() = default;
};
void ASection::accept(Visitor& visitor) {
visitor.visitASection(*this);
}
void BSection::accept(Visitor& visitor) {
visitor.visitBSection(*this);
}
class CleanerA : public Visitor {
public:
void visitASection(ASection& A) override {
std::cout << "代号为A的清洁工正在打扫公园A区垃圾 " << std::endl;
std::cout << "A区员工回应: ";
A.cleanA();
}
void visitBSection(BSection& B) override {}
};
class CleanerB : public Visitor {
public:
void visitASection(ASection& A) override {}
void visitBSection(BSection& B) override {
std::cout << "代号为B的清洁工正在打扫公园B区垃圾 " << std::endl;
std::cout << "B区员工回应: ";
B.cleanB();
}
};
class ParkManager : public Visitor {
public:
void visitASection(ASection& A) override {
std::cout << "公园管理者正在检查公园A区卫生 " << std::endl;
}
void visitBSection(BSection& B) override {
std::cout << "公园管理者正在检查公园B区卫生 " << std::endl;
}
};
class Supervisor : public Visitor {
public:
void visitASection(ASection& A) override {
std::cout << "上级领导正在视察公园A区卫生情况 " << std::endl;
}
void visitBSection(BSection& B) override {
std::cout << "上级领导正在视察公园B区卫生情况 " << std::endl;
}
};
class Tourist : public Visitor {
public:
void visitASection(ASection& A) override {
std::cout << "啊,多么美丽的A区! " << std::endl;
}
void visitBSection(BSection& B) override {
std::cout << "啊,多么美丽的B区! " << std::endl;
}
};
class Park {
public:
void addElement(ParkElement* element) {
elements.push_back(element);
}
void accept(Visitor& visitor) {
for (auto* element : elements) {
element->accept(visitor);
}
}
void simulateParkEnvironment() {
printf("嗨,我是这座美丽的公园,一个被绿意环绕,处处洋溢着生命力的地方。当我睁开我那翠绿的眼帘,清晨的光线轻柔地投射在我的脸颊,唤醒了我沉睡的灵魂。\n");
printf("我拥抱着蜿蜒的小径,它们如血管般在我的身体里延伸,引领着人们踏上通往宁静与和谐的旅程。我是树木的家园,高大的树干是我的骨骼,茂盛的枝叶是我在蓝天上的画布。在这里,鸟儿在我的枝头歌唱,它们是我最美的音符。\n");
printf("我的湖泊是一位深邃的诗人,它用清澈的眼眸凝视着蓝天白云,倒映出四季的更替。我是花朵的花园,每一朵花都在我的怀抱中绽放,散发着它们的芬芳,吸引着蜜蜂与蝴蝶在我的世界中翩翩起舞。\n");
printf("我是孩子们的游乐园,我是情侣们的约会地,我是家庭们的野餐场。我是自然的博物馆,也是文化的聚集所。这里,每个人都能找到属于自己的一片天地,无论是独处静思,还是与朋友共享美好时光。\n");
printf("在我温柔的怀抱中,时间似乎变得缓慢而宁静。每一次呼吸,都是对大自然馈赠的感谢。我邀请你,快来探访我,和我一起沉醉在美丽的自然与宁静的和谐之中。\n");
}
private:
std::vector<ParkElement*> elements;
};
void doWorking() {
Park park;
ASection A;
BSection B;
park.addElement(&A);
park.addElement(&B);
CleanerA cleanerA;
CleanerB cleanerB;
ParkManager manager;
Supervisor supervisor;
Tourist tourist;
std::cout << "\n=== Park self-introduction ===" << std::endl;
park.simulateParkEnvironment();
std::cout << "\n=== Cleaning Phase ===" << std::endl;
park.accept(cleanerA);
park.accept(cleanerB);
std::cout << "\n=== Inspection Phase ===" << std::endl;
park.accept(manager);
park.accept(supervisor);
std::cout << "\n=== Travel Phase ===" << std::endl;
park.accept(tourist);
return;
}
int main() {
doWorking();
return 0;
}
运行效果
三、访问者模式应用场景
1. 对象结构稳定,但需要增加新操作
当你有一个相对稳定的对象结构(如复杂的对象组合或树形结构),但需要频繁添加新的操作时,访问者模式非常适用。通过引入访问者,可以轻松扩展操作而无需修改对象结构。
2. 需要对不同类型的对象执行相似操作
如果你需要对多个不同类型的对象执行相似的操作,但这些对象的具体类型各不相同,访问者模式可以将操作的实现集中在访问者类中。例如,在图形处理应用中,可以定义一个访问者对不同形状(如圆形、矩形、三角形)进行统一的处理。
3. 复杂的对象结构遍历
当需要对复杂对象结构进行遍历并对每个对象执行某些操作时,访问者模式提供了一种清晰的方式。访问者可以定义多个重载的访问方法,使得对不同类型的对象执行操作时更为直观。
4. 需要在不同操作间共享代码
访问者模式允许不同的操作通过同一个访问者类进行代码重用,从而避免在对象类中重复实现相同的操作逻辑。这在处理大量类时特别有用,有助于减少代码冗余。
5. 降低对象之间的耦合性
使用访问者模式,可以将操作的定义与对象的实现分开,降低了对象之间的耦合性。这使得对象可以更加独立,便于进行单元测试和维护。
四、总结
访问者模式在需要对对象结构进行多次扩展操作而不希望修改对象结构本身的场景下非常有效。通过引入访问者,可以方便地添加新操作。值得注意的是访问者模式可能会破坏类的封装性,因为访问者模式要求访问者对象能够访问并调用每一个元素对象的操作,这有时会导致元素对象必须暴露一些内部操作和状态。这种暴露可能会破坏对象的封装性,从而增加系统的复杂性和潜在的错误风险。