C++访问者模式及其实现(非常详细)
访问者模式(Visitor Pattern)在不修改已有对象结构的前提下定义新的操作,也就是在不改变数据的结构的情况下能够自由地添加或者移除操作。该模式能够将数据结构和操作解耦,使操作可以独立变化。
访问者模式的核心思想是将操作封装在访问者对象中,而不是封装在元素对象中。元素对象通过接受访问者对象的访问,将自身信息传递给访问者对象,从而完成操作。这种方式可以在不修改元素对象的前提下,增加新的操作,具有较好的扩展性。
使用访问者模式的一个典型场景是处理对象结构中的各个对象,但根据不同的访问者对象,操作会有所不同。
访问者模式增加新的操作非常方便,只需创建新的具体访问者类;将相关操作集中到访问者对象中,使元素对象和操作解耦,提高了系统的灵活性。可以对对象结构进行多种不同的操作,而无须修改元素对象结构。

图 1 传统访问者模式UML简图

图 2 C++11模板访问者模式UML简图
模板类 visitor 是定义的基础模板接口,实际实现时可以使用特化或者继承方式实现 concreteVisitor 类作为业务中使用的类。DATA_ITFC 是作为模板参数传递给 visitor 的数据访问接口定义。实际数据的访问接口需要延迟到业务中实现。
访问者模式 visitor 模板类中维护了一个函数对象表,该对象表可以使用 std::string 进行检索获取,代码如下:
addMethod() 方法用来增加新的处理方法;函数 eraseMethod() 用来移除数据处理方法。利用 std::unordered_set 和函数对象配合,增删处理方法就可以像增删普通数据一样方便,代码如下:
对下标运算符重载提供了更加简便的调用方法的方式,例如可以使用 ["abc"](xxx) 的方式调用已经添加的方法,代码如下:
函数 call() 用来指定方法索引调用一个明确的方法来处理一条数据内容。函数 callEach() 则可以指定数据范围调用一个明确的方法进行处理,代码如下:
在 main() 函数中首先通过 addMethod() 方法添加处理函数,然后通过下标运算符调用对应的处理函数。也可以通过 callEach() 方法访问指定范围的数据内容,代码如下:
访问者模式的核心思想是将操作封装在访问者对象中,而不是封装在元素对象中。元素对象通过接受访问者对象的访问,将自身信息传递给访问者对象,从而完成操作。这种方式可以在不修改元素对象的前提下,增加新的操作,具有较好的扩展性。
使用访问者模式的一个典型场景是处理对象结构中的各个对象,但根据不同的访问者对象,操作会有所不同。
访问者模式增加新的操作非常方便,只需创建新的具体访问者类;将相关操作集中到访问者对象中,使元素对象和操作解耦,提高了系统的灵活性。可以对对象结构进行多种不同的操作,而无须修改元素对象结构。
传统访问者模式
传统访问者模式由几个核心组件组成:元素(Element)、具体元素(Concrete Element)、访问者(Visitor)、具体访问者(Concrete Visitor)和对象结构(Object Structure),如下图所示。
图 1 传统访问者模式UML简图
- 元素定义了一个接受访问者对象的操作接口,通常包含一个 accept() 方法,用于接受访问者对象的访问;
- 具体元素实现了元素接口,提供了具体的实现逻辑;
- 访问者定义了访问元素对象的操作接口,通常包含多个重载方法,每种方法用于访问不同类型的元素对象;
- 具体访问者实现了访问者接口,提供了具体的操作逻辑,每种方法实现对不同类型元素对象进行处理。对象结构包含一组元素对象,提供了遍历元素对象的方法,以及与访问者的交互方法。
C++11元编程下的访问者模式
使用 C++11 元编程技术可以使用模板参数将数据和方法独立出来。利用函数对象可以方便灵活地增加和移除数据处理方法,如下图所示。
图 2 C++11模板访问者模式UML简图
模板类 visitor 是定义的基础模板接口,实际实现时可以使用特化或者继承方式实现 concreteVisitor 类作为业务中使用的类。DATA_ITFC 是作为模板参数传递给 visitor 的数据访问接口定义。实际数据的访问接口需要延迟到业务中实现。
C++访问者模式实现和解析
模板类 visitor 的两个模板参数 DATA_T 和 RET 分别是数据对象类型和访问者函数的返回值类型,其基本原理是利用容器和函数对象实现对处理方法进行存储管理,从而实现针对数据的增删处理方法。访问者模式和解释器模式配合起来可以实现非常灵活的处理系统。访问者模式 visitor 模板类中维护了一个函数对象表,该对象表可以使用 std::string 进行检索获取,代码如下:
template< typename DATA_ITFC,typename RET >
class visitor
{
public:
using dataItem_t = typename std::remove_pointer<
typename std::decay< DATA_ITFC >::type >::type;
using func_t = std::function< RET(dataItem_t&) >;
//函数对象容器类型,使用了std::unordered_map来记录。这种方式有很高
//的访问速度
using funcTan_t = typename std::unordered_map< std::string,func_t >;
protected:
funcTan_t m_funcs__;
public:
visitor(){}
virtual~visitor(){}
addMethod() 方法用来增加新的处理方法;函数 eraseMethod() 用来移除数据处理方法。利用 std::unordered_set 和函数对象配合,增删处理方法就可以像增删普通数据一样方便,代码如下:
bool addMethod(const std::string&name,func_t func)
{
auto rst = m_funcs__.insert(std::make_pair(name,func));
return rst.second;
}
bool eraseMethod(const std::string&name)
{
auto it = m_funcs__.find(name);
if(it){
m_funcs__.erase(it);
return true;
}
return false;
}
对下标运算符重载提供了更加简便的调用方法的方式,例如可以使用 ["abc"](xxx) 的方式调用已经添加的方法,代码如下:
func_t operator[](const std::string&name){
auto it = m_funcs__.find(name);
if(it){
return it->second;
}
return{};
}
函数 call() 用来指定方法索引调用一个明确的方法来处理一条数据内容。函数 callEach() 则可以指定数据范围调用一个明确的方法进行处理,代码如下:
RET call(const std::string&name,dataItem_t&data)
{
auto it = m_funcs__.find(name);
if(it){
return it->second(data);
}
return{};
}
template< typename InputIterator >
void callEach(const std::string&name,
InputIterator begin,InputIterator end)
{
auto it = m_funcs__.find(name);
for(auto it1 = begin;it1! = end; ++it1){
it->second(* it1);
}
}
//判断是否存在指定索引的方法
bool has(const std::string&name)
{
auto it = m_funcs__.find(name);
return it! = m_funcs__.end();
}
};
C++访问者模式应用示例
在下面的示例中结构体作为被访问的数据类。函数 func1() 和 func2() 分别是两个具体访问的处理函数。在 main() 函数中首先通过 addMethod() 方法添加处理函数,然后通过下标运算符调用对应的处理函数。也可以通过 callEach() 方法访问指定范围的数据内容,代码如下:
#include <iostream>
#include <string>
#include "designM/vistor.hpp"
// 自定义数据类型
struct Data {
Data(int value) : value(value) {}
int value;
};
// 处理函数1
int func1(Data& data) {
std::cout << "func1 called with value: " << data.value << std::endl;
return data.value + 1;
}
// 处理函数2
int func2(Data& data) {
std::cout << "func2 called with value: " << data.value << std::endl;
return data.value * 2;
}
int main() {
// 创建 visitor 对象,数据类型为 Data,返回类型为 int
visitor<Data, int> myVisitor;
// 将方法添加到 visitor 对象
myVisitor.addMethod("func1", func1);
myVisitor.addMethod("func2", func2);
// 创建数据对象
Data myData(10);
// 调用指定名称的方法处理数据
int result1 = myVisitor["func1"](myData);
std::cout << "Result1: " << result1 << std::endl;
int result2 = myVisitor["func2"](myData);
std::cout << "Result2: " << result2 << std::endl;
// 批量处理数据
std::vector<Data> dataVec = {Data(20), Data(30)};
myVisitor.callEach("func1", dataVec.begin(), dataVec.end());
return 0;
}
程序的运行结果如下:
func1 called with value:10
Result1:11
func2 called with value:10
Result2:20
func1 called with value:20
func1 called with value:30
ICP备案:
公安联网备案: