C++ 设计模式——代理模式

代理模式(Proxy Pattern)是一种结构型设计模式,通过代理对象控制对其他对象的访问。代理模式通常用于控制对某个对象的访问,提供额外的功能,如延迟加载、访问控制、日志记录等。

其主要实现思想是在客户端和真正要访问的对象之间引入一个代理对象(间接层),于是,以往客户端对真正对象的访问现在变成了通过代理对象进行访问,代理对象在这里起到了一个中介或者桥梁作用。引人代理对象的主要目的是可以为客户端增加额外的功能、约束或针对客户端的调用屏蔽一些复杂的细节问题。

1. 主要组成成分

  • 抽象主题(Subject):定义了真实对象和代理对象的共同接口。
  • 真实主题(Real Subject):实现了抽象主题接口,代表实际的业务逻辑。
  • 代理(Proxy):持有对真实主题的引用,并实现了抽象主题接口。代理可以在访问真实主题之前或之后添加额外的功能。

2. 逐步构建代理模式

以下是一个简单的代理模式示例,通过浏览器访问某个网站,最简单的方式就是在浏览器中输入网站的地址来直接访问。

2.1 抽象主题类定义

首先创建一个基类 WebAddr,代表要访问的网站。

class WebAddr
{
public:
    virtual void visit() = 0; //执行访问网站的动作,子类中重新实现
    virtual ~WebAddr() {} //做父类时析构函数应该为虚函数
};
2.2 真实主题类实现

创建两个网站类 WebAddr_ShoppingWebAddr_Video 继承自 WebAddr

//某购物网站
class WebAddr_Shopping :public WebAddr
{
public:
    virtual void visit()
    {
        //......访问该购物网站,可能涉及到复杂的网络通信
        cout << "访问WebAddr_Shopping购物网站!" << endl;
    }
};

//某视频网站
class WebAddr_Video :public WebAddr
{
public:
    virtual void visit()
    {
        //......访问该视频网站,可能涉及到复杂的网络通信
        cout << "访问WebAddr_Video视频网站!" << endl;
    }
};
2.3 代理类实现

引入代理类 WebAddrProxyWebAddr_Shopping_Proxy

//网站代理类
class WebAddrProxy :public WebAddr
{
public:
    //构造函数,引入的目的是传递进来要访问的具体网站
    WebAddrProxy(WebAddr* pwebaddr) :mp_webaddr(pwebaddr) {}
public:
    virtual void visit()
    {
        //在这里进行访问的合法性检查、日志记录或者流量限制......
        mp_webaddr->visit();
        //在这里可以进行针对返回数据的过滤......
    }
private:
    WebAddr* mp_webaddr; //代表要访问的具体网站
};

//专门针对某购物网站WebAddr_Shopping的代理
class WebAddr_Shopping_Proxy :public WebAddr
{
public:
    virtual void visit()
    {
        //在这里进行访问的合法性检查、日志记录或者流量限制......
        WebAddr_Shopping* p_webaddr = new WebAddr_Shopping();
        p_webaddr->visit();
        //在这里可以进行针对返回数据的过滤......

        //释放资源
        delete p_webaddr;
    }
};
2.4 主函数
int main()
{
    WebAddr* wba1 = new WebAddr_Shopping();
    WebAddr* wba2 = new WebAddr_Video();

    WebAddrProxy* wbaproxy1 = new WebAddrProxy(wba1);
    wbaproxy1->visit(); //通过代理去访问WebAddr_Shopping购物网站

    WebAddrProxy* wbaproxy2 = new WebAddrProxy(wba2);
    wbaproxy2->visit(); //通过代理去访问WebAddr_Video视频网站

    WebAddr_Shopping_Proxy* wbasproxy = new WebAddr_Shopping_Proxy();
    wbasproxy->visit(); //访问的实际是某购物网站

    //资源释放
    delete wba1;
    delete wba2;
    delete wbaproxy1;
    delete wbaproxy2;
    //资源释放
    delete wbasproxy;

    return 0;
}

3. 代理模式 UML 图

代理模式 UML 图

代理模式 UML 图解析
  • Subject(抽象主题):该类定义了真实主题与代理主题的共同接口,使得在使用真实主题的地方都可以使用代理主题。这里指的是 WebAddr 类。
  • Proxy(代理主题):该类内部包含对真实主题的引用,可以对真实主题进行访问。代理主题中提供与真实主题相同的接口(如 visit),以达到可以取代真实主题的目的。这里指的是 WebAddrProxy 类。
  • RealSubject(真实主题):定义代理主题所代表的真实对象,真正的业务逻辑在真实主题中实现。客户端(如 main 函数)通过代理主题间接访问真实主题中的接口。这里指的是 WebAddr_ShoppingWebAddr_Video 类。

4. 代理模式的优点

  • 控制访问:代理可以控制对真实对象的访问,增加安全性。
  • 延迟加载:可以在需要时才创建真实对象,节省资源。
  • 增强功能:可以在访问真实对象前后添加额外的功能,如日志记录、缓存等。

5. 代理模式的缺点

  • 增加复杂性:引入代理可能会增加系统的复杂性,尤其是在管理代理和真实对象时。
  • 性能开销:代理可能会引入额外的性能开销,尤其是在处理大量请求时。

6. 代理模式的分类

  • 远程代理 (Remote Proxy):用于控制对远程对象的访问,通常用于网络编程中,例如通过网络调用远程服务。
  • 虚拟代理 (Virtual Proxy):用于延迟加载资源,避免不必要的开销。只有在真正需要时才创建真实对象,如大文件或复杂对象的加载。
  • 保护代理 (Protection Proxy):用于控制对对象的访问权限,增强安全性。可以根据不同用户的权限来决定是否允许访问某些方法或数据。
  • 缓存/缓冲代理 (Cache Proxy):用于缓存频繁访问的数据,以减少计算或网络请求的开销。例如,缓存数据库查询结果或API响应。
  • 智能引用代理 (Smart Reference Proxy):用于管理对象的生命周期,例如引用计数或智能指针。可以确保对象在不再需要时被正确释放,避免内存泄漏。
  • 写时代理 (Write Proxy):用于延迟写入操作,直到需要时才执行。可以用于优化性能,尤其在处理大量数据时。

7. 代理模式和装饰者模式比较

代理模式和装饰者模式都是结构型设计模式,但它们的目的和使用场景有所不同。以下是两者的比较:

7.1 代理模式 (Proxy Pattern)
  • 目的:通过代理对象控制对真实对象的访问,提供额外的功能(如访问控制、延迟加载、日志记录等)。
  • 主要角色
    • Subject:定义真实对象和代理对象的共同接口。
    • Real Subject:实现了 Subject 接口,代表实际的业务逻辑。
    • Proxy:持有对真实主题的引用,控制对真实主题的访问。
  • 使用场景
    • 远程对象访问。
    • 资源的延迟加载。
    • 访问权限控制。
    • 添加额外功能(如缓存、日志)。
7.2 装饰者模式 (Decorator Pattern)
  • 目的:动态地给对象添加额外的功能,而不改变其结构。装饰者模式允许通过组合的方式来扩展对象的功能。
  • 主要角色
    • Component:定义一个接口,用于实现具体的对象和装饰者。
    • Concrete Component:实现了 Component 接口的具体对象。
    • Decorator:持有一个 Component 对象的引用,并实现了 Component 接口。可以在调用方法时添加额外的功能。
  • 使用场景
    • 动态添加功能或行为。
    • 需要扩展类的功能而不使用子类。
    • 组合多个装饰器以实现复杂的功能。
7.3 主要区别
  • 代理模式 侧重于控制对对象的访问,主要用于安全性、性能优化等场景。
  • 装饰者模式 则侧重于在不修改对象结构的情况下动态地扩展对象的功能,适用于需要灵活组合功能的场景。
特点代理模式 (Proxy)装饰者模式 (Decorator)
目的控制对真实对象的访问动态增加对象的功能
结构包含真实对象的引用包含一个或多个装饰者
功能扩展主要用于访问控制、延迟加载等主要用于功能扩展和组合
使用方式通过代理对象访问真实对象通过装饰者对象增强组件
透明性代理通常对客户端透明装饰器通常对客户端透明

总结

代理模式通过引入代理对象来控制对真实对象的访问,提供了额外的功能,如延迟加载和访问控制。它在许多场景中都非常有用,但也可能增加系统的复杂性。在使用代理模式时,需要根据具体需求权衡其优缺点。

完整代码

#include <iostream>
#include <vector>
#include <string>
#include <fstream>

using namespace std;

class WebAddr
{
public:
    virtual void visit() = 0; //执行访问网站的动作,子类中重新实现
    virtual ~WebAddr() {} //做父类时析构函数应该为虚函数
};

//某购物网站
class WebAddr_Shopping :public WebAddr
{
public:
    virtual void visit()
    {
        //......访问该购物网站,可能涉及到复杂的网络通信
        cout << "访问WebAddr_Shopping购物网站!" << endl;
    }
};

//某视频网站
class WebAddr_Video :public WebAddr
{
public:
    virtual void visit()
    {
        //......访问该视频网站,可能涉及到复杂的网络通信
        cout << "访问WebAddr_Video视频网站!" << endl;
    }
};

//---------------------------
//网站代理类
class WebAddrProxy :public WebAddr
{
public:
    //构造函数,引入的目的是传递进来要访问的具体网站
    WebAddrProxy(WebAddr* pwebaddr) :mp_webaddr(pwebaddr) {}
public:
    virtual void visit()
    {
        //在这里进行访问的合法性检查、日志记录或者流量限制......
        mp_webaddr->visit();
        //在这里可以进行针对返回数据的过滤......
    }
private:
    WebAddr* mp_webaddr; //代表要访问的具体网站
};

//专门针对某购物网站WebAddr_Shopping的代理
class WebAddr_Shopping_Proxy :public WebAddr
{
public:
    virtual void visit()
    {
        //在这里进行访问的合法性检查、日志记录或者流量限制......
        WebAddr_Shopping* p_webaddr = new WebAddr_Shopping();
        p_webaddr->visit();
        //在这里可以进行针对返回数据的过滤......

        //释放资源
        delete p_webaddr;
    }
};

int main()
{
    WebAddr* wba1 = new WebAddr_Shopping();
    WebAddr* wba2 = new WebAddr_Video();

    WebAddrProxy* wbaproxy1 = new WebAddrProxy(wba1);
    wbaproxy1->visit(); //通过代理去访问WebAddr_Shopping购物网站

    WebAddrProxy* wbaproxy2 = new WebAddrProxy(wba2);
    wbaproxy2->visit(); //通过代理去访问WebAddr_Video视频网站

    WebAddr_Shopping_Proxy* wbasproxy = new WebAddr_Shopping_Proxy();
    wbasproxy->visit(); //访问的实际是某购物网站

    //资源释放
    delete wba1;
    delete wba2;
    delete wbaproxy1;
    delete wbaproxy2;
    //资源释放
    delete wbasproxy;

    return 0;
}
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐