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

C++代理模式及其实现(非常详细)

代理模式是一种通过创建一中间对象来间接地访问目标对象的模式,从而控制对目标对象的访问。

代理模式的核心思想是将对目标对象的访问通过代理对象进行中间处理和控制。代理对象可以对目标对象进行补充或增强,并且隐藏目标对象的具体实现。这种方式可以实现对目标对象的访问控制、延迟加载、缓存、日志记录或者对数据进行加密等操作。

代理模式可以提供对目标对象的访问控制,可以在代理对象中添加额外的逻辑以进行权限控制等。代理模式可以实现延迟加载,只有当真正需要访问目标对象时才会进行加载,从而提高系统性能。代理模式可以实现缓存功能,当代理对象接收到请求时,可以先从缓存中获取结果,避免重复计算。代理模式可以实现日志记录、异常处理等功能,以便对目标对象进行增强。

由于代理模式引入了代理对象,所以会增加系统的复杂度。由于代理对象需要实现与目标对象相同的接口,所以可能会导致代码冗余。在动态代理中,在运行时创建代理对象,性能相对较低。

传统代理模式

代理模式通常涉及 3 个角色,即目标对象(Subject)、代理对象(Proxy)和客户端(Client),如下图所示:


图 1 传统代理模式UML简图

在传统模式下通常代理和实际目标类都需要从抽象目标类继承,使用同样的一套接口。采用这种方式实现的代理方式的通用性比较差,也不适合对已有代码进行改造。

C++11元编程下的结构设计

利用 C++11 元编程技术,对抽象目标对象和实际目标对象进行解耦。利用函数对象将代理类和目标关联起来。

在实现一般意义上的代理模式后,利用 C++11 的 std::future 功能实现了异步代理的方式。在模块中进而使用了智能指针 std::shared_ptr 管理具体目标对象,可以更加有效地管理内存,结构如下图所示:


图 2 C++模板代理模式UML简图

C++代理模式实现和解析

整个实现主要分成 4 部分。

第 1 部分,private__ 名字空间用来约束 itfcBase,使之不暴露给外部使用。这个类主要用来检查接口的继承关系。在自己的工程中尽量不要故意地破坏这种约束关系,从而进一步地破坏进程关系检查逻辑,代码如下:
//designM/proxy.hpp
namespace private__
{
    struct itfcBase{};
}

第 2 部分,在模板类中定义了唯一一个接口函数,在实际使用时代理操作需要实现这个函数。

模板参数 RET 是接口函数返回值类型,PARAMS 是可以传递给接口函数参数类型表。这个模板类从 private__::itfcBase 继承,从而保证了代理操作接口的血统,代码如下:
//designM/proxy.hpp

template< typename RET,typename...PARAMS >
struct proxyItfc:public private__::itfcBase
{
    virtual RET agent(PARAMS&&...args) = 0;
};

第 3 部分实现了 3个宏,用来方便地定义代理操作接口,以弥补第 2 部分中只有一个接口函数且函数名称固定的不足:
要求在实际的业务中必须实现对应的接口,宏实现的代码如下:
//designM/proxy.hpp

#define DECLARE_PROXY_ITFC(name)  \
struct name:public private__::itfcBase{

#define PROXY_ITFC_MTD(ret,name,...)virtual ret name(__VA_ARGS__) =0;

#define END_PROXY_ITFC() };

第 4 部分是实际代理模板类的实现。模板参数中 ITFC_TYPE 是代理操作接口类,也就是上面描述的部分;CONCREATE_TYPE 是要代理的目标模板类,这个目标类不需要对其进行修改,这样方便对已有代码进行代理操作而不需要对原有代码进行修改,代码如下:
//designM/proxy.hpp

template< typename ITFC_TYPE,typename CONCREATE_TYPE >
class proxy:public ITFC_TYPE
{
public:
    using itfc_t = typename std::remove_pointer<typename std::decay< ITFC_TYPE >::type >::type;
    using concrete_t = typename std::remove_pointer<typename std::decay< CONCREATE_TYPE >::type >::type;

使用 static_assert 代码检查代理接口的继承关系。使用智能指针 std::shared_ptr<concrete_t> 保存实际代理目标对象。对于元函数 std::remove_pointer<> 和元函数 std::decay<> 前面已经多次解释,此后便不再重复说明其功用,代码如下:
//designM/proxy.hpp

static_assert(std::is_base_of<itfc_t,private__::itfcBase >::value,"");
protected:
    std::shared_ptr< concrete_t >pt_cncrt__; //实际代理的目标类对象指针

public:

get() 函数用于获取代理目标对象指针。需要注意的是,get() 函数内部使用 std::weak_ptr 隔离 std::shared_ptr<concrete_t>,以避免实际使用时出现交叉引用,代码如下:
//designM/proxy.hpp

std::shared_ptr< concrete_t >
get()
{
    std::weak_ptr ret(pt_cncrt__);
    if(!ret.expired()){
        return ret.lock();
    }
    return{};
}
proxy(){}

通过这个构造函数可以传入已经实例化的代理目标,不需要额外地对被代理对象进行构造操作,代码如下:
//designM/proxy.hpp

proxy(std::shared_ptr< concrete_t > ptr):pt_cncrt__(ptr){}
virtual~proxy(){}

create() 函数用来实例化被代理目标。使用模板函数,并且使用变长模板能够保证兼容不同的初始化参数,代码如下:
//designM/proxy.hpp
template< typename...Args >
bool create(Args&&...args)
{
    bool ret = true;
    try{
        pt_cncrt__ = std::make_shared<concrete_t >(std::forward<Args >(args)...);
    }catch(std::bad_alloc&){
        ret = false;
    }catch(...){ //这里用来防止在代理目标中抛出其他异常
        ret = false;
    }
    return ret;
}

函数 agentCall() 实现了一个异步调用方式,用来应对处理时间比较长的情况。使用函数对象传入处理操作方法,能够提供灵活的接口处理方式。最后的操作接口通过 std::future 返回。同样这个实现也是用变长模板函数实现的,以保证适应不同参数传递的情况。

在传入的函数对象的参数表中 std::shared_ptr<concrete_t> 是目标对象指针;可以在函数对象中使用目标对象,PARAMS 则是传递参数类型列表,代码如下:
//designM/proxy.hpp

template< typename Func_t,typename...PARAMS >
auto agentCall(Func_t&&cb,PARAMS&&...args )  -> std::future<typename std::result_of<Func_t(std::shared_ptr< concrete_t >,PARAMS&&...) >::type >
{
using return_type = typename std::result_of< Func_t(std::shared_ptr< concrete_t >,PARAMS&&...) >::type;
元函数 std::result_of<> 主要用来推断函数或仿函数的返回类型,其工作原理是把函数调用拆分成函数类型和函数参数两部分来处理。例如,对于函数类型为 int(int) 的函数对象,std::result_of<int(int)>::type 会得到其返回类型int。

然而,元函数 std::result_of 只能处理函数指针类型、函数引用类型和函数式类(仿函数类),不能处理函数类型,所以 std::result_of 在 C++17 中已经废弃使用,改用 std::invoke_result。

agentCall 使用返回值推导的方式定义返回值,返回值的具体类型通过 std::future 的内部类型 type 进行推导。

采用返回值类型推导和模板参数中的可变参数,使 agentCall 函数能够适用于绝大多数数据的情况。

std::packaged_task<>() 用于包装任何可调用的目标(例如函数、Lambda 表达式、bind 表达式、函数对象),以便它被异步调用,其返回值或抛出的异常被存储于能通过 std::future 对象访问的共享状态中。

使用 std::packaged_task 对传入的函数对象进行处理以生成任务对象 task,后续返回 std::future 对象,然后使用 std::thread 执行 task,最后在外部通过 std::future 获取函数执行的返回值结果,代码如下:
//designM/proxy.hpp

    std::packaged_task< return_type() >
        task(std::bind(std::forward<Func_t >(cb),pt_cncrt__,
                   std::forward<PARAMS >(args)...));
        //从task获取std::future对象实例
        auto ret = task.get_future();
        //异步执行任务
        std::thread thd(std::move(task));
        thd.detach();
        //返回std::future对象
        return ret;
    }
};

C++代理模式应用示例

同步调用方式是最简单的方式。使用时可以通过特化 proxyItfc 明确一个接口类型,在实现具体代理类时将这个特化的类作为模板参数传入,并实现代理函数 agent,代码如下:
//proxy1.cpp

#include<iostream >
#include<sstream >

#include"designM/proxy.hpp"

using namespace wheels;
using namespace dm;

//itfc定义了一个代理函数的代理接口类
//proxy1.cpp

using itfc =proxyItfc<int,const std::string& >;

//这个是代理目标类
class abc{
public:
    int dosth(const std::string&a,int b){
        std::cout<< "abc::dosth"<< a<< ""<< b<< std::endl;
        return 3;
    }
};

定义具体代理类,从模板函数进行继承并实现接口函数。模板参数 itfc 是 proxyItfc 特化后的类,使用 itfc 特化 proxy 后在 proxy 中就有了虚函数 agent。在具体代理类中就需要实现虚函数 agent 作为代理处理的接口方法,代码如下:
//proxy1.cpp

class myProxy:public proxy< itfc,abc >
{
public:
    //实现接口函数,这个函数在itfc中声明了
    virtual int agent(const std::string&str)override{
        std::cout<< "myProxy::agent"<< str<< std::endl;
        int param1 = 12;
        std::stringstream ss;
        ss<< str<< ":"<< param1;
        auto ptr = get__();
        if(ptr){
            param1 =12/ptr->dosth(ss.str(),12);
        }
        return param1;
    }
};

int main()
{
    myProxy my_proxy;         //实例化代理类
    my_proxy.create();        //实例化代理目标
    //执行代理操作
    int rst = my_proxy.agent("abc");
    std::cout<< rst<< std::endl;
    return 0;
}

在代理模块中定义了一组方便自定义接口的宏,使用宏定义接口的同步方式方便快速地定义接口类,代码如下:
//proxy.cpp

#include<iostream >
#include"designM/proxy.hpp"
using namespace wheels;
using namespace dm;

//定义一个接口,MyInterface是接口类的名字
DECLARE_PROXY_ITFC(MyInterface)
    PROXY_ITFC_MTD(int,methodItfc)

声明接口函数,函数的返回值是 int,名字是 methodItfc,这个函数没有参数。这个宏展开后的内容如下:
virtual int methodItfc() = 0
然后关闭接口声明,定义代理目标类,代码如下:
//proxy.cpp

END_PROXY_ITFC();

//定义一个代理目标实现类
class MyImplementation {
public:
    virtual int methodName()override{
        std::cout<< "Mission is done int subject object"<< std::endl;
        return 42;
    }
};
实现具体代理类,需要实现接口声明中的所有接口,接口类型通过 proxy 的第 1 个模板参数进行模板特化,代码如下:
//proxy.cpp

class myProxy:public proxy<MyInterface,MyImplementation >
{
public:
    int methodItfc()override
    {
        int ret = 12 * get__()->methodName();
        return ret;
    }
};

int main()
{
    //创建代理类
    myProxy my_proxy;   //创建实现类对象
    //调用代理类的方法
    my_proxy.create();
    auto result = my_proxy.methodItfc();
    std::cout<< "after proxy modified result:"<< result<< std::endl;
    return 0;
}
运行结果如下:

Mission is done int subject object
after proxy modified result:504


代理模块支持以异步的方式自行代理操作。成员函数 gentCall 通过传入回调函数和函数的参数提交异步操作,并通过 std::future 获取执行结果,代码如下:
//proxy2.cpp

#include<iostream >
#include<sstream >
#include"designM/proxy.hpp"
using namespace wheels;
using namespace dm;
//这里声明一个空的接口类型,用来占位proxy的接口参数
DECLARE_PROXY_ITFC(MyInterface)
END_PROXY_ITFC();
//定义代理对象
class MyImplementation{
public:
    std::string methodName(int data){
        std::stringstream ss;
        ss<< "data to str:"<< data;
        return ss.str();
    }
};

int main()
{
    //创建代理类
    using MyProxy = proxy<MyInterface,MyImplementation >;
    MyProxy myProxy;         //创建实现类对象
    myProxy.create();        //创建代理对象

使用 agentCall 异步调用代理操作,agentCall 返回的是 std::future 对象,如果函数有返回值,则可以异步地返回结果,代码如下:
//proxy2.cpp

    auto result = myProxy.agentCall([](std::shared_ptr<MyImplementation > ptr, int a)
    {
        std::cout<<"in async mission"<< std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
        std::cout<<"finishing async mission"<< std::endl;
        return ptr->methodName(a);
    },12);
    std::cout<<"async mission is stared"<< std::<< std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    //通过get()方法获取执行结果
    std::cout<< result.get()<<std::endl;
    return 0;
}
可以看出代码先输出“async mission is stared”,然后输出了异步任务的两处内容,等待所有的任务都执行完成后通过 std::future 的 get() 方法获取执行结果,执行结果如下:

async mission is stared
in async mission
finishing async mission
data to str:12

相关文章