C++ 代理模式(Proxy Pattern)
目录
- 模式定义与核心思想
- 解决的核心业务痛点
- 标准三大角色 & UML结构说明
- 代理模式核心分类(5种)
- C++ 标准静态代理完整实现
- 五种代理类型 C++ 实战代码
- 静态代理 与 动态代理 详解
- 代理 VS 装饰器 VS 适配器 深度辨析
- 适用场景 & 禁用场景
- 优缺点精细拆解
- C++ 工程开发最佳实践规范
- 常见坑点与完整避坑指南
- 面试高频深挖题(含标准答案)
- C++ 开源框架 & 实际项目落地
1. 模式定义与核心思想
定义
代理模式是结构型设计模式 ,给真实业务对象 提供一个代理占位对象 ,由代理对象控制对真实对象的访问,客户端始终访问代理,间接调用真实对象。
核心特征
- 代理与真实对象实现同一个抽象接口
- 客户端无感知,完全透明
- 代理可以前置拦截、后置处理、延迟创建、权限校验、缓存结果
- 不修改真实对象任何代码,符合开闭原则
生活化类比
- 明星经纪人:粉丝找经纪人 → 经纪人对接明星
- 房产中介:客户找中介 → 中介对接房东
- 网络代理:本机请求 → 代理服务器 → 目标网站
核心一句话
不直接访问真身,通过中间人统一管控访问逻辑。
2. 解决的核心业务痛点
- 真实对象创建成本极高
加载大图、初始化连接、读取大配置、数据库复杂查询,没必要提前初始化。 - 需要做访问权限控制
不同角色只能访问指定接口,统一在代理层拦截校验。 - 重复请求浪费资源
相同查询、相同计算结果可缓存,代理层统一缓存。 - 需要统一横切逻辑
日志、耗时、限流、鉴权、监控,全部放在代理层,不侵入业务。 - 隐藏真实对象实现细节
对外只暴露代理,屏蔽底层复杂逻辑与远程地址。 - 远程对象调用
RPC、微服务跨进程调用,本地代理占位,转发网络请求。
3. 标准三大角色 & UML结构
| 角色名称 | 英文 | 职责详解 |
|---|---|---|
| 抽象主题 | Subject | 统一抽象接口,代理类、真实类必须共同实现 |
| 真实主题 | RealSubject | 真正实现业务逻辑的核心对象 |
| 代理类 | Proxy | 持有真实对象引用/指针,实现相同接口,控制访问、附加逻辑 |
调用流程
客户端 → 抽象接口 → 代理Proxy → 前置处理 → 调用真实对象 → 后置处理 → 返回客户端
4. 代理模式核心分类(5种常用)
- 静态代理:编译期写死代理类,一对一代理
- 虚拟代理:懒加载,用到时才创建真实对象
- 保护代理:权限拦截,控制不同角色访问
- 缓存代理:缓存重复计算/查询结果,提升性能
- 远程代理:本地占位,代理远程跨进程/网络对象
5. C++ 标准静态代理完整实现
5.1 抽象主题接口
cpp
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <ctime>
// 抽象主题
class Subject
{
public:
virtual ~Subject() = default;
virtual void request(const std::string& info) = 0;
};
5.2 真实主题
cpp
// 真实主题
class RealSubject : public Subject
{
public:
void request(const std::string& info) override
{
std::cout << "真实对象执行业务:" << info << "\n";
}
};
5.3 静态代理类
cpp
// 静态代理
class StaticProxy : public Subject
{
private:
std::unique_ptr<RealSubject> realObj;
public:
void request(const std::string& info) override
{
// 前置增强
std::cout << "代理:前置日志记录\n";
// 懒创建真实对象
if (!realObj)
{
realObj = std::make_unique<RealSubject>();
}
// 调用真实业务
realObj->request(info);
// 后置增强
std::cout << "代理:后置统计收尾\n";
}
};
5.4 客户端调用
cpp
int main()
{
std::unique_ptr<Subject> sub = std::make_unique<StaticProxy>();
sub->request("用户业务请求");
return 0;
}
6. 五种代理类型 C++ 实战代码
6.1 虚拟代理(懒加载)
适用:大图、大资源、重初始化对象。
cpp
class BigResource
{
public:
void load()
{
std::cout << "加载超大资源,耗时很久...\n";
}
void show()
{
std::cout << "展示资源内容\n";
}
};
class VirtualProxy
{
private:
mutable std::unique_ptr<BigResource> res;
public:
void show()
{
if (!res)
{
res = std::make_unique<BigResource>();
res->load();
}
res->show();
}
};
6.2 保护代理(权限控制)
cpp
enum class UserRole
{
Admin,
Normal
};
class SecretData
{
public:
void view()
{
std::cout << "查看敏感机密数据\n";
}
};
class ProtectProxy
{
private:
UserRole role;
std::unique_ptr<SecretData> data;
public:
explicit ProtectProxy(UserRole r) : role(r), data(std::make_unique<SecretData>()) {}
void view()
{
if (role != UserRole::Admin)
{
std::cout << "权限不足,禁止访问\n";
return;
}
data->view();
}
};
6.3 缓存代理(结果缓存)
cpp
class QueryService
{
public:
std::string query(const std::string& key)
{
// 模拟耗时查询
std::cout << "数据库真实查询:" << key << "\n";
return key + "_result";
}
};
class CacheProxy
{
private:
QueryService service;
std::unordered_map<std::string, std::string> cache;
public:
std::string query(const std::string& key)
{
if (cache.find(key) != cache.end())
{
std::cout << "代理:命中缓存,直接返回\n";
return cache[key];
}
std::string res = service.query(key);
cache[key] = res;
return res;
}
};
6.4 远程代理(模拟)
本地代理占位,屏蔽网络请求细节,客户端只调本地接口。
cpp
class RemoteService
{
public:
void remoteCall()
{
std::cout << "远程服务器执行业务\n";
}
};
class RemoteProxy
{
public:
void remoteCall()
{
std::cout << "代理:建立网络连接、封装协议\n";
RemoteService().remoteCall();
std::cout << "代理:关闭连接、解析返回\n";
}
};
6.5 智能引用代理
C++ std::shared_ptr/unique_ptr 就是典型智能代理:
- 生命周期管理
- 引用计数
- 自动析构释放
7. 静态代理 与 动态代理
静态代理
- 编译期手动编写代理类
- 一个代理只能代理一个真实类
- 类膨胀严重,新增业务需新增代理
- C++ 无原生动态代理,只能手动模拟
动态代理
- 运行时动态生成代理类
- 一个代理可代理多个目标类
- Java 有 JDK 动态代理 / Cglib
- C++ 可通过 模板、泛型、宏 简易实现通用代理
8. 代理 VS 装饰器 VS 适配器 深度辨析
| 模式 | 核心意图 | 接口是否改变 | 侧重点 |
|---|---|---|---|
| 代理模式 | 控制访问、隔离拦截 | 接口完全一致 | 权限、懒加载、缓存、隐藏真身 |
| 装饰器模式 | 动态叠加功能增强 | 接口完全一致 | 日志、加配料、多层扩展 |
| 适配器模式 | 接口转换兼容 | 接口发生改变 | 适配旧接口、第三方不兼容接口 |
一句话精准区分
- 想管控访问、隔离真身 → 代理
- 想层层加功能、动态扩展 → 装饰器
- 想接口不匹配、做转换适配 → 适配器
关键细节区别
- 代理倾向自己创建真实对象,重在控制;
- 装饰器由外部传入被装饰对象,重在组合增强。
9. 适用场景 & 禁用场景
适用场景
- 对象创建/初始化成本高,需要懒加载
- 需要统一权限校验、访问控制
- 频繁重复查询/计算,需要缓存代理
- RPC、微服务、跨进程远程调用
- 需要统一日志、耗时、限流、监控横切逻辑
- 隐藏真实业务实现,对外屏蔽细节
- 控制对象生命周期、引用计数管理
禁用场景
- 业务逻辑极其简单,无任何管控、增强需求
- 对象创建开销极小,没必要懒加载
- 接口需要大幅改造,应使用适配器而非代理
10. 优缺点精细拆解
优点
- 职责分离:真实对象只关注核心业务,代理处理管控逻辑
- 透明无感:客户端完全不用感知代理存在
- 开闭原则:新增代理逻辑不修改真实业务代码
- 性能优化:懒加载、缓存大幅节省资源
- 安全隔离:权限拦截、隐藏真实对象地址与实现
- 统一管控:所有请求收口在代理层,便于统一治理
缺点
- 增加代理类,系统类数量变多
- 多一层调用链路,轻微性能损耗
- 多层代理嵌套会增加调试难度
- 静态代理会造成代理类泛滥
11. C++ 工程开发最佳实践规范
- 统一采用抽象接口 + 智能指针 实现静态代理
- 懒加载场景使用
mutable修饰内部真实对象指针,保证 const 语义合法 - 代理类只做拦截、控制、增强,不写核心业务逻辑
- 优先使用组合持有真实对象,不滥用继承
- 通用横切逻辑(日志/耗时)封装为通用模板代理
- 控制代理嵌套层数,建议不超过2层
- 多线程懒加载使用
std::call_once保证线程安全
12. 常见坑点与完整避坑指南
- 坑:代理与真实对象不统一接口
避坑:严格继承同一抽象 Subject,保证透明替换。 - 坑:裸指针管理不当导致内存泄漏
避坑:全程使用std::unique_ptr/shared_ptr。 - 坑:在代理中修改真实业务逻辑
避坑:代理只做前置后置,不侵入核心逻辑。 - 坑:滥用多层代理嵌套
避坑:层级越少越好,避免调用链复杂难调试。 - 坑:简单业务强行上代理模式
避坑:无管控、无懒加载、无缓存需求直接调用原对象即可。
13. 面试高频深挖题(标准答案)
Q1:代理模式的核心原理与作用?
通过引入代理中间层,控制客户端对真实对象的访问,实现懒加载、权限拦截、缓存、日志、远程调用,接口保持不变,不修改真实业务代码。
Q2:静态代理和动态代理区别?
静态代理编译期手动编写,一对一代理,类易膨胀;动态代理运行时动态生成,一个代理适配多个目标类。C++无原生动态代理,可用模板模拟通用代理。
Q3:代理和装饰器最核心区别?
代理重在访问控制与隔离 ,通常自行创建真实对象;装饰器重在功能叠加增强,由外部传入被装饰对象,可无限嵌套组合。
Q4:虚拟代理解决什么问题?
解决大对象初始化开销大问题,实现延迟加载,只有真正使用时才创建初始化,节省内存与启动时间。
Q5:C++ 中哪些是代理模式的应用?
std::shared_ptr/unique_ptr 智能指针、RPC本地桩、图片懒加载、权限拦截器都是代理模式思想。
14. C++ 实际项目落地场景
- 网络框架:RPC客户端本地代理、请求拦截代理
- 配置系统:大配置文件懒加载虚拟代理
- 权限系统:接口访问保护代理,统一鉴权
- 数据查询:数据库查询缓存代理
- 游戏开发:大资源、模型、贴图懒加载代理
- 中间件:请求日志、限流、监控统一代理层