新谈设计模式 Chapter 12 — 代理模式 Proxy
Chapter 12 — 代理模式 Proxy灵魂速记房产中介——替房东干活但你以为自己在跟房东打交道。秒懂类比你想租房直接找房东太难了——房东可能在国外。于是你找中介代理。中介和房东对你来说接口一样都能带看房、签合同。但中介在中间做了额外事情收你中介费、帮你筛选、替你跟房东谈价。代理和真实对象实现相同接口客户端分不出来自己在跟谁说话。问题引入// 灾难现场加载巨大图片classImage{public:Image(conststd::stringfilename){// 构造时就从磁盘加载 50MB 的图片loadFromDisk(filename);// 耗时 3 秒}voiddisplay(){/* 显示 */}};// 问题页面上有 100 张图打开页面就全部加载// 用户可能只看前 3 张就关了……代理的四种类型类型干什么类比虚代理延迟加载懒加载缩略图先顶着点开才加载原图保护代理权限控制保安没有工卡不让进远程代理本地对象代表远程对象VPN你以为在本地上网缓存代理缓存结果避免重复计算搜索引擎缓存模式结构┌───────────────┐ ┌───────────────┐ │ Subject │ │ Subject │ │ (接口) │ │ (接口) │ ├───────────────┤ ├───────────────┤ │ request() │ │ request() │ └───────┬───────┘ └───────┬───────┘ │ │ ┌────┴─────┐ ┌─────┴──────┐ │RealSubject│←────────│ Proxy │ │(真实对象) │ has-a │ (代理) │ │request() │ │request() │ └──────────┘ └────────────┘ 在调用前后加逻辑C 实现虚代理懒加载大图#includeiostream#includememory#includestring// 接口 classImage{public:virtual~Image()default;virtualvoiddisplay()const0;};// 真实对象加载很慢 classHighResImage:publicImage{public:explicitHighResImage(std::string filename):filename_(std::move(filename)){loadFromDisk();// 构造时加载很慢}voiddisplay()constoverride{std::cout 显示高清图片: filename_\n;}private:voidloadFromDisk(){std::cout ⏳ 从磁盘加载 filename_ (耗时 3 秒)...\n;}std::string filename_;};// 虚代理延迟加载 classImageProxy:publicImage{public:explicitImageProxy(std::string filename):filename_(std::move(filename)){std::cout 创建图片代理: filename_ (零成本)\n;}voiddisplay()constoverride{if(!realImage_){std::cout [代理] 首次访问现在才加载...\n;realImage_std::make_uniqueHighResImage(filename_);}realImage_-display();}private:std::string filename_;mutablestd::unique_ptrHighResImagerealImage_;// 懒加载// 为什么要 mutable因为 display() 是 const 方法不改变代理的逻辑状态// 但首次调用时需要初始化 realImage_。mutable 允许在 const 方法中修改这个成员。// 这是 lazy initialization 的标准用法。};intmain(){std::cout 创建 3 张图片用代理\n;ImageProxyimg1(photo_50MB.jpg);ImageProxyimg2(wallpaper_80MB.png);ImageProxyimg3(screenshot_20MB.bmp);std::cout\n 只显示第 1 张 \n;img1.display();// 这时才真正加载std::cout\n 再显示第 1 张 \n;img1.display();// 已经加载过了直接显示std::cout\n 第 2、3 张从未被访问从未被加载 \n;// img2 和 img3 的真实图片从未被创建节省了 100MB 加载时间}输出 创建 3 张图片用代理 创建图片代理: photo_50MB.jpg (零成本) 创建图片代理: wallpaper_80MB.png (零成本) 创建图片代理: screenshot_20MB.bmp (零成本) 只显示第 1 张 [代理] 首次访问现在才加载... ⏳ 从磁盘加载 photo_50MB.jpg (耗时 3 秒)... 显示高清图片: photo_50MB.jpg 再显示第 1 张 显示高清图片: photo_50MB.jpg 第 2、3 张从未被访问从未被加载 保护代理权限控制classSecureDocumentProxy:publicDocument{public:SecureDocumentProxy(std::shared_ptrDocumentdoc,std::string userRole):doc_(std::move(doc)),role_(std::move(userRole)){}std::stringread()constoverride{// 所有人都能读returndoc_-read();}voidwrite(conststd::stringcontent)override{if(role_!admin){std::cout❌ 权限不足只有 admin 能写入\n;return;}doc_-write(content);// 只有 admin 能走到这里}private:std::shared_ptrDocumentdoc_;std::string role_;};缓存代理classCachingProxy:publicDataSource{public:explicitCachingProxy(std::shared_ptrDataSourcesource):source_(std::move(source)){}std::stringquery(conststd::stringsql)override{if(autoitcache_.find(sql);it!cache_.end()){std::cout[缓存命中] sql\n;returnit-second;}std::cout[缓存未命中查询数据库] sql\n;autoresultsource_-query(sql);cache_[sql]result;returnresult;}private:std::shared_ptrDataSourcesource_;std::unordered_mapstd::string,std::stringcache_;};什么时候用类型✅ 适合场景虚代理大对象延迟加载保护代理权限控制、访问限制远程代理RPC、网络服务调用缓存代理数据库查询缓存、API 响应缓存日志代理记录方法调用日志智能引用std::shared_ptr就是防混淆Proxy vs DecoratorProxyDecorator目的控制访问增强功能谁控制生命周期代理自己管理真实对象客户端组装装饰层嵌套通常单层可以多层嵌套接口和真实对象完全相同和被装饰对象相同一句话分清Proxy 管门禁Decorator 加配置。Proxy vs AdapterProxyAdapter接口相同不同转换目的控制访问兼容接口Proxy vs FacadeProxyFacade对象数代理一个对象包装多个子系统接口和原对象相同定义新的简化接口现代 C 中的代理std::shared_ptr本身就是一种智能引用代理——管理原始指针的生命周期。// shared_ptr 就是 RawPointer 的代理// 接口相同- 和 *但加了引用计数和自动释放std::shared_ptrWidgetptrstd::make_sharedWidget();ptr-doSomething();// 你以为在直接操作 Widget其实经过了 shared_ptr 代理七大结构型模式总结模式一句话关键词Adapter接口不合我来转转换Bridge两个维度分开变分离Composite树形统一操作递归Decorator层层加功能包装Facade一键搞定简化Flyweight共享省内存池化Proxy控制访问管门禁代理