简介
访问者模式(Visitor Pattern)是一种行为型设计模式,它可以在不改变被访问对象的结构的前提下,定义作用于这些对象的新操作。
访问者模式用于解耦对象结构和对象的操作。通过将操作分离到访问者对象中,对象结构可以保持稳定,而新增、修改操作可以通过新增、修改访问者对象来实现。
描述
访问者模式由两类对象组成:元素对象(Element)和访者对象(Visitor)。元素对象有一个接受访问者的方法,而访问者对象使用这个方法来访问元素对象。
原理
访问者模式的核心思想是将操作从元素对象中分离出来,放到访问者对象中。访问者对象可以定义多个访问方法,每个方法对应一个具体的操作。元素对象有一个接受访问者对象的方法,在这个方法中,元素对象将自己作为参数传递给访问者对象的相应问方法。
类图
示例
假设我们有一个表示不同种类动物的对象结构。这个对象结构包含不同的动物对象,比如狗、猫和鸟,每个动物对象都有一个 accept() 方法用于接受访问者对象的访问。
class Animal;
class Visitor{
public:
virtual void visit(dog* dog) = 0;
virtual void visit(Cat* cat) = 0;
virtual void visit(Bird* bird) = 0;
};
class Animal {
public:
virtual void accept(Visitor* visitor) = 0;
};
class Dog : public Animal {
public:
void accept(Visitor* visitor) {
visitor->visit(this);
}
};
class Cat : public Animal {
public:
void accept(Visitor* visitor) {
visitor->visit(this);
}
};
class Bird : Animal {
public:
void accept(Visitor* visitor) {
visitor->visit(this);
}
};
在这个示例中,定义了一个访问者接口 Visitor,其中包含了三个访问方法:visit(Dog* dog),visit(Cat* cat) 和 visit(Bird* bird)。Animal 是一个抽象类,定义了一个接受访问者的方法 accept(Visitor* visitor)。Dog、Cat 和 Bird 是具体的动物类,都实现了 accept() 方法,并将自己传递给访问者的相应方法。
此外,还需要实现访问者对象,例如:
class SoundVisitor : public Visitor {
public:
void visit(Dog* dog) {
std::cout << "Dog sound: woof!" << std::endl;
}
void visit(Cat* cat) {
std::cout << "Cat sound: meow!" << std::endl;
}
void visit(Bird*) {
std::cout << "Bird sound: tweet!" << std::endl;
};
在这个示例,实现了一个问者对象 SoundVisitor,它重载了 Visitor 接口中的三个访问方法,并在每个方法中输出相应动物的声音。
现在,可以创建动物对象和访问者对象使用访问者对象来访问不同的动物对象。
Animal* dog = new Dog();
Animal* cat = new Cat();
Animal* bird = new Bird();
Visitor* visitor = new SoundVisitor();
dog->accept(visitor); // 输出结果:Dog sound: woof!
cat->accept(visitor); // 输出结果:Cat sound: meow!
bird->accept(visitor); // 输出结果:Bird sound: tweet!
解释
在上述示例中,创建了一个访问者对象 SoundVisitor,并创建了个动物对象:狗、猫和鸟。然后,我们调用了动物对象的 accept(),并将访问者对象传递给它们。每个动物对象接受了访问者对象,并调用访问者对象的相应访问方法。
因此,最终的输出结果为:
Dog sound: woof!
Cat sound: meow!
Bird sound: tweet!
结论
访问者模式能够在不改变对象结构的情况下定义新的操作它将操作从对象结构中分离出来,使得操作可以独立变化。同时,访问者模式也遵循了开闭原则,通过新增访问者对象来扩展操作。
应用场景
访问者模式适于以下场景:
- 当对象结构比较稳定,但经常需要定义新操作时。
- 当一组对象需要进行不同的操作,而且你希望封装这些操作而不暴露给对象具体实现时。