代理模式(Proxy)

定义

代理是一种结构型设计模式让你能够提供对象的替代品或其占位符代理控制着对于原对象的访问,并允许在将请求提交给对象前后进行一些处理

前言

1. 问题

举个例子:有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它,并非总是需要。

你可以实现延迟初始化:在实际有需要时再创建该对象对象的所有客户端都要执行延迟初始代码。不幸的是,这很可能会带来很多重复代码。 在理想情况下,我们希望将代码直接放入对象的类中,但这并非总是能实现:比如类可能是第三方封闭库的一部分。

2. 解决方案

代理模式建议新建一个与原服务对象接口相同的代理类,然后更新应用以将代理对象传递给所有原始对象客户端。代理类接收到客户端请求后会创建实际的服务对象,并将所有工作委派给它。

代理将自己伪装成数据库对象,可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。

这有什么好处呢?如果需要在类的主要业务逻辑前后执行一些工作,你无需修改类就能完成这项工作。由于代理实现的接口与原类相同,因此你可将其传递给任何一个使用实际服务对象的客户端。

结构

  1. 服务接口(Service Interface)声明了服务接口。代理必须遵循该接口才能伪装成服务对象。
  2. 服务(Service)类提供了一些实用的业务逻辑
  3. 代理(Proxy)类包含一个指向服务对象的引用成员变量代理完成其任务(例如延迟初始化、记录日志、访问控制和缓存等)后会将请求传递给服务对象。通常情况下,代理会对其服务对象的整个生命周期进行管理。
  4. 客户端(Client) 能通过同一接口与服务或代理进行交互,所以你可在一切需要服务对象的代码中使用代理。

适用场景

  • 延迟初始化(虚拟代理)。如果你有一个偶尔使用的重量级服务对象,一直保持该对象运行会消耗系统资源时,可使用代理模式。

你无需在程序启动时就创建该对象,可将对象的初始化延迟到真正有需要的时候。

  • 访问控制(保护代理)。如果你只希望特定客户端使用服务对象,这里的对象可以是操作系统中非常重要的部分,而客户端则是各种已启动的程序(包括恶意程序),此时可使用代理模式。

代理可仅在客户端凭据满足要求时将请求传递给服务对象。

  • 本地执行远程服务(远程代理)。适用于服务对象位于远程服务器上的情形。

在这种情形中,代理通过网络传递客户端请求,负责处理所有与网络相关的复杂细节。

  • 记录日志请求(日志记录代理)。适用于当你需要保存对于服务对象的请求历史记录时。代理可以在向服务传递请求前进行记录。
  • 缓存请求结果(缓存代理)。适用于需要缓存客户请求结果并对缓存生命周期进行管理时,特别是当返回结果的体积非常大时。

代理可对重复请求所需的相同结果进行缓存,还可使用请求参数作为索引缓存的键值。

  • 智能引用。当一个对象被引用时提供一些额外的操作,比如将对象被调用的次数记录下来等。可在没有客户端使用某个重量级对象时立即销毁该对象

代理会将所有获取了指向服务对象或其结果的客户端记录在案。代理会时不时地遍历各个客户端,检查它们是否仍在运行。如果相应的客户端列表为空,代理就会销毁该服务对象,释放底层系统资源。代理还可以记录客户端是否修改了服务对象。其他客户端还可以复用未修改的对象。

实现方式

  1. 如果没有现成的服务接口,你就需要创建一个接口来实现代理和服务对象的可交换性。从服务类中抽取接口并非总是可行的,因为你需要对服务的所有客户端进行修改,让它们使用接口。备选计划是将代理作为服务类的子类,这样代理就能继承服务的所有接口了。
  2. 创建代理类,其中必须包含一个存储指向服务的引用的成员变量。通常情况下,代理负责创建服务并对其整个生命周期进行管理。在一些特殊情况下,客户端会通过构造函数将服务传递给代理。
  3. 根据需求实现代理方法。在大部分情况下,代理在完成一些任务后应将工作委派给服务对象。
  4. 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。 你可以在代理类中创建一个简单的静态方法,也可以创建一个完整的工厂方法。
  5. 可以考虑为服务对象实现延迟初始化。

优点

  • 你可以在客户端毫无察觉的情况下控制服务对象。
  • 如果客户端对服务对象的生命周期没有特殊要求,你可以对生命周期进行管理。
  • 即使服务对象还未准备好或不存在,代理也可以正常工作。
  • 开闭原则。你可以在不对服务或客户端做出修改的情况下创 建新代理。

缺点

  • 代码可能会变得复杂,因为需要新建许多类。
  • 服务响应可能会延迟。

ServiceInterface.hpp

#ifndef C9D296A5_E3CD_49BA_973E_BEC89D4C1B83
#define C9D296A5_E3CD_49BA_973E_BEC89D4C1B83
#include <string>
using namespace std;
class ThirdPartyTVLib{public:virtual string listVideos() = 0;virtual string getVideoInfo(int id) = 0;
};#endif /* C9D296A5_E3CD_49BA_973E_BEC89D4C1B83 */

Service.hpp

#ifndef B13EB0FC_4951_4363_BAD4_7033A6E36357
#define B13EB0FC_4951_4363_BAD4_7033A6E36357
#include "string"
#include "ServiceInterface.hpp"class ThirdPartyTVClass : public ThirdPartyTVLib {public:std::string listVideos() override {// 向远程视频后端服务发送一个API请求获取视频信息, 这里忽略实现return "video list";}std::string getVideoInfo(int id) override {// 向远程视频后端服务发送一个API请求获取某个视频的元数据, 这里忽略实现return "video info";}
};#endif /* B13EB0FC_4951_4363_BAD4_7033A6E36357 */

 Proxy.hpp

#ifndef BBF7D2AD_221E_4784_BDA3_BA134E2FC787
#define BBF7D2AD_221E_4784_BDA3_BA134E2FC787#include <string>
#include "ServiceInterface.hpp"// 为了节省网络带宽, 我们可以将请求缓存下来并保存一段时间
// 当代理类接受到真实请求后才会将其委派给服务对象
class CachedTVClass : public ThirdPartyTVLib {public:explicit CachedTVClass(ThirdPartyTVLib* service) : service_(service), need_reset_(false), list_cache_(""), video_cache_("") {}void reset() {need_reset_ = true;}std::string listVideos() override {if (list_cache_ == "" || need_reset_) {list_cache_ = service_->listVideos();}return list_cache_;}std::string getVideoInfo(int id) override {if (video_cache_ == "" || need_reset_) {video_cache_ = service_->getVideoInfo(id);}return video_cache_;}private:ThirdPartyTVLib* service_;std::string list_cache_;std::string video_cache_;bool need_reset_;
};#endif /* BBF7D2AD_221E_4784_BDA3_BA134E2FC787 */

Client.hpp

#ifndef D9959BB2_43B6_4562_99CE_1CCADDFEC5AC
#define D9959BB2_43B6_4562_99CE_1CCADDFEC5AC#include <string>
#include <cstdio>
#include "Service.hpp"// 之前直接与服务对象交互的 GUI 类不需要改变, 前提是它仅通过接口与服务对象交互。
// 我们可以安全地传递一个代理对象来代替真实服务对象, 因为它们都实现了相同的接口。
class TVManager {public:explicit TVManager(ThirdPartyTVLib* s) : service_(s) {}void renderVideoPage(int id) {std::string video_info = service_->getVideoInfo(id);// 渲染视频页面, 这里忽略实现printf("渲染视频页面: %s\n", video_info.c_str());return;}void renderListPanel() {std::string videos = service_->listVideos();// 渲染视频缩略图列表, 这里忽略实现printf("渲染视频缩略图列表: %s\n", videos.c_str());return;}private:ThirdPartyTVLib* service_;
};
#endif /* D9959BB2_43B6_4562_99CE_1CCADDFEC5AC */

main.cpp

#include "Client.hpp"
#include "Service.hpp"
#include "Proxy.hpp"int main() {ThirdPartyTVClass* aTVService = new ThirdPartyTVClass();CachedTVClass* aTVProxy = new CachedTVClass(aTVService);TVManager* manager = new TVManager(aTVProxy);manager->renderVideoPage(1);manager->renderListPanel();delete aTVService;delete aTVProxy;delete manager;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/5091.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Stable Diffusion 使用outpainting扩展图像

通常SD可以应用绘制的图像是固定分辨率的&#xff0c;但是如果想要超出这个分辨率的话那就要使用一些方法&#xff0c;并且如果还想保持这些图片的连贯性&#xff0c;例如背景就要使用到outpainting。 文章目录 操作流程准备图像 操作流程 准备图像 首先将需要扩展边缘的图像…

Xcode 15 beta 2 - Apple Vision Pro安装下载

1. 更新OS系统 已更新的忽略这步 2. 下载Xcode 15 beta 2 Xcode 15 Xcode 15 让你能够开发、测试和分发适用于所有 Apple 平台的 App。借助增强的代码补齐功能、交互式预览和实时动画&#xff0c;更快地推进 App 的编码和设计。利用 Git 暂存功能直接改进下次要提交的文件&…

二叉树各种函数的实现

如果你觉得迷茫&#xff0c;那就尽可能选择比较困难的路。 目录 前言&#xff1a; &#x1f340;一.通过前序遍历创建二叉树 &#x1f341;二.二叉树的四种遍历 &#x1f342;1.二叉树的前序遍历 &#x1f33c;2.二叉树的中序遍历 &#x1f34c;3.二叉树的后序遍历 …

如何在 ZBrush 和 UE5 中创建精灵吟游诗人(P2)

小伙伴们大家好&#xff0c;下篇咱们精彩继续&#xff0c;主要是讲解角色建模完成后的重新拓扑、UV、灯光设置和渲染。 纹理的重新拓扑和UV 一切都在 Maya 中完成&#xff0c;重新拓扑是一个漫长的过程&#xff0c;因为角色有很多元素&#xff0c;值得庆幸的是&#xff0c;在…

从0到1精通自动化测试,pytest自动化测试框架,fixture之autouse=True(十二)

一、前言 平常写自动化用例会写一些前置的fixture操作&#xff0c;用例需要用到就直接传该函数的参数名称就行了。当用例很多的时候&#xff0c;每次都传这个参数&#xff0c;会比较麻烦 fixture里面有个参数autouse&#xff0c;默认是Fasle没开启的&#xff0c;可以设置为Tr…

【排序算法】堆排序

堆与一维数组 建立堆与一维数组的联系 堆排序并不是直接对堆节点Node类型排序&#xff0c;而是通过建立索引之间的关系&#xff0c;对一维数组排序。 称之为堆排序&#xff0c;是因为节点索引值之间的关系与完全二叉树的非常类似&#xff0c;而树又称堆。 设根节点为i&#xff…

2023.7.2-【for语言】:输入一个整数,并输入该整数对应个数的整数,求他们的和与平均值

程序&#xff1a; int a;int b0;int c;int sum0;double ave;printf("请输入待求整数的个数&#xff1a;");scanf("%d",&a);for (b 1; b<a; b){printf("整数%d&#xff1a;", b);scanf("%d", &c);sum c;}printf("以上…

【ChatGpt】解决视频框交换中的平滑过渡的问题

【ChatGpt】解决视频框交换中的平滑过渡的问题 问题抽象chatgpt 看看直接给参考代码 解决效果 问题 在视频的播放中&#xff0c;我们想调换下容器的位置 &#xff0c;在互调的过程中&#xff0c;如果需要重新进行数据的初始化&#xff0c;获取与加载&#xff0c;就会很慢&…

Java 多线程

java多线程 第一种方法&#xff1a; 1.自定义线程类继承Thread类 2.重写run()方法 3.创建线程对象&#xff0c;调用start()方法启动线程 第二种方法&#xff1a; 1.实现Runnable()接口 2.重写run()方法 3.创建线程对象&#xff0c;调用start()启动线程

机器学习李宏毅学习笔记34

文章目录 前言一、Knowledge distillation二、Parameter quantization三、Architecture design四、Dynamic computation总结 前言 神经网络压缩&#xff08;二&#xff09;其他方法 一、Knowledge distillation 先train一个大的network叫做teacher network&#xff0c;小的ne…

selenium-wire简介

目录 前言&#xff1a; 一.简介 二.用法 1.安装selenium-wire库 2.获取请求信息 3.获取响应信息 4.实例 前言&#xff1a; Selenium Wire 是一个 Python 库&#xff0c;它扩展了 Selenium WebDriver 的功能&#xff0c;使其能够轻松地拦截、修改和检查浏览器与服务器之…

【Vue3】学习笔记-watch函数

与Vue2.x中watch配置功能一致 两个小“坑”&#xff1a; 监视reactive定义的响应式数据时&#xff1a;oldValue无法正确获取、强制开启了深度监视&#xff08;deep配置失效&#xff09;。监视reactive定义的响应式数据中某个属性时&#xff1a;deep配置有效。 <template&…