一、访问者模式介绍
访问者模式是设计模式中十分重要的一种模式,在学习OSG的过程中,OSG在节点访问(节点遍历)的设计中,就使用了访问者模式,通过查找资料,理解如下,如有错误还请指正。
本文的一些概念和举例主要参考了以下两篇博文,感谢原作者精彩的讲解。
1.适用场景
对象结构中对象对应的类很少改变,但经常需要在此对象结构上定义新的操作。
2.结构模式:
- (1)Visitor(抽象访问者):在C++中,一般为一个抽象类,在Java中,一般为一个接口,抽象访问者的作用是声明访问者可以访问哪些元素,具体到程序中就是visit方法中(在OSG中是apply方法)参数定义哪些对象是可以被访问的。在OSG的节点访问设计中,使用了访问者模式,其中NodeVisitor类定义了哪些类可以访问。
在NodeVisitor类的定义中,我们可以看到,重载了很多apply方法(如下图),这些都是visitor方法,如果在节点访问(遍历)的过程中,要对某种类型的节点进行操作,那就重载这类节点的apply方法。
- (2)ConcreteVisitor(具体访问者):继承自抽象访问者,实现抽象访问者所声明的方法,它影响到访问者访问者访问到一个类后该干什么。在OSG的节点访问中,一个类继承自NodeVisitor,重写apply方法,就能在节点遍历过程中实现一些用户定义的操作。
- (3)Element(抽象元素类):在C++中,一般为一个抽象类,在Java中,一般为一个接口,声明接受哪一类访问者访问,定义一个accept操作,通常以一个Visitor作为参数。在OSG的节点访问结构中,Node就是抽象元素类,其中的虚函数accept方法,确定了可以接受NodeVisitor类的访问。在Node类中,我们可以看到accept方法:
- (4)ConcreteElement(具体元素类):继承自Element,实现accept操作,通过accept参数Visitor的visitor(OSG中为apply)方法来实现对元素的访问。在OSG的节点访问结构中,继承自Node的子类,如Geode、Group就是具体元素类。因为OSG中场景组织结构为树的结构,所以针对每一类节点,OSG都应该重写accept方法,实现了树结构的访问。
3.具体的案例分析
在访问西安时,访问者会参观各个景点。对于景点来说,无论访问者是谁,它们都是不变的。而作为访问者,不同角色的访问方式也不尽相同,游客只负责旅游 - 吃喝玩乐,而清洁工则需要打扫卫生、清理垃圾。
这里,游客和清洁工是具体访问者,兵马俑、钟楼等景点是具体元素,西安这座城市是结构对象。
代码实现
1.创建访问者
访问者需要为每个景点都提供一个访问方法:
/ visitor.h
#ifndef VISITOR_H
#define VISITOR_H
class BellTower;
class TerracottaWarriors;
// 访问者
class IVisitor
{
public:
virtual ~IVisitor() {}
virtual void Visit(BellTower *) = 0;
virtual void Visit(TerracottaWarriors *) = 0;
};
#endif // VISITOR_H
2.创建具体访问者
具体访问者有两种 - 游客、清洁工,它们分别实现了不同的访问操作(游客只管吃喝玩乐,清洁工负责清理垃圾):
#include "visitor.h"
#include "concrete_element.h"
// 游客
class Tourist : public IVisitor
{
public:
virtual void Visit(BellTower *) override {
std::cout << "I'm visiting the Bell Tower!" << std::endl;
}
virtual void Visit(TerracottaWarriors *) override {
std::cout << "I'm visiting the Terracotta Warriors!" << std::endl;
}
};
// 清洁工
class Cleaner : public IVisitor
{
public:
virtual void Visit(BellTower *) override {
std::cout << "I'm cleaning up the garbage of Bell Tower!" << std::endl;
}
virtual void Visit(TerracottaWarriors *) override {
std::cout << "I'm cleaning up the garbage of Terracotta Warriors!" << std::endl;
}
};
3.创建元素
景点中定义了一个 Accept() 接口,用于接受访问者的访问:
class IVisitor;
// 地方
class IPlace
{
public: virtual ~IPlace() {}
virtual void Accept(IVisitor *visitor) = 0;
};
4.创建具体元素
具体元素有两个 - 钟楼、兵马俑,它们实现了 Accept() 方法:
#include "element.h"
#include "visitor.h"
#include <iostream>
// 钟楼
class BellTower : public IPlace
{
public:
virtual void Accept(IVisitor *visitor) override {
std::cout << "Bell Tower is accepting visitor." << std::endl;
visitor->Visit(this);
}
};
// 兵马俑
class TerracottaWarriors : public IPlace
{
public:
virtual void Accept(IVisitor *visitor) override {
std::cout << "Terracotta Warriors is accepting visitor." << std::endl;
visitor->Visit(this);
}
};
注意: 在 Accept() 方法中,通过调用 Visitor 的 visit() 方法(以当前对象为参数)来实现对景点的访问。