首页 > 编程笔记 > C++笔记 阅读:3

C++访问者模式及其实现(非常详细)

访问者模式(Visitor Pattern)在不修改已有对象结构的前提下定义新的操作,也就是在不改变数据的结构的情况下能够自由地添加或者移除操作。该模式能够将数据结构和操作解耦,使操作可以独立变化。

访问者模式的核心思想是将操作封装在访问者对象中,而不是封装在元素对象中。元素对象通过接受访问者对象的访问,将自身信息传递给访问者对象,从而完成操作。这种方式可以在不修改元素对象的前提下,增加新的操作,具有较好的扩展性。

使用访问者模式的一个典型场景是处理对象结构中的各个对象,但根据不同的访问者对象,操作会有所不同。

访问者模式增加新的操作非常方便,只需创建新的具体访问者类;将相关操作集中到访问者对象中,使元素对象和操作解耦,提高了系统的灵活性。可以对对象结构进行多种不同的操作,而无须修改元素对象结构。

传统访问者模式

传统访问者模式由几个核心组件组成:元素(Element)、具体元素(Concrete Element)、访问者(Visitor)、具体访问者(Concrete Visitor)和对象结构(Object Structure),如下图所示。


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

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

相关文章