7. 结构型模式 - 代理模式

亦称: Proxy

 意图

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

代理设计模式

 问题

为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。

代理模式解决的问题

数据库查询有可能会非常缓慢。

你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。

在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。

 解决方案

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

代理模式的解决方案

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

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

 真实世界类比

信用卡是一大捆现金的代理

信用卡和现金在支付过程中的用处相同。

信用卡是银行账户的代理, 银行账户则是一大捆现金的代理。 它们都实现了同样的接口, 均可用于进行支付。 消费者会非常满意, 因为不必随身携带大量现金; 商店老板同样会十分高兴, 因为交易收入能以电子化的方式进入商店的银行账户中, 无需担心存款时出现现金丢失或被抢劫的情况。

 代理模式结构

代理设计模式的结构

  1. 服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。

  2. 服务 (Service) 类提供了一些实用的业务逻辑。

  3. 代理 (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。

    通常情况下, 代理会对其服务对象的整个生命周期进行管理。

  4. 客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。

 伪代码

本例演示如何使用代理模式在第三方腾讯视频 (TencentVideo, 代码示例中记为 TV) 程序库中添加延迟初始化和缓存。

代理模式示例的结构

使用代理缓冲服务结果。

程序库提供了视频下载类。 但是该类的效率非常低。 如果客户端程序多次请求同一视频, 程序库会反复下载该视频, 而不会将首次下载的文件缓存下来复用。

代理类实现和原下载器相同的接口, 并将所有工作委派给原下载器。 不过, 代理类会保存所有的文件下载记录, 如果程序多次请求同一文件, 它会返回缓存的文件。

// 远程服务接口。
interface ThirdPartyTVLib ismethod listVideos()method getVideoInfo(id)method downloadVideo(id)// 服务连接器的具体实现。该类的方法可以向腾讯视频请求信息。请求速度取决于
// 用户和腾讯视频的互联网连接情况。如果同时发送大量请求,即使所请求的信息
// 一模一样,程序的速度依然会减慢。
class ThirdPartyTVClass implements ThirdPartyTVLib ismethod listVideos() is// 向腾讯视频发送一个 API 请求。method getVideoInfo(id) is// 获取某个视频的元数据。method downloadVideo(id) is// 从腾讯视频下载一个视频文件。// 为了节省网络带宽,我们可以将请求结果缓存下来并保存一段时间。但你可能无
// 法直接将这些代码放入服务类中。比如该类可能是第三方程序库的一部分或其签
// 名是`final(最终)`。因此我们会在一个实现了服务类接口的新代理类中放入
// 缓存代码。当代理类接收到真实请求后,才会将其委派给服务对象。
class CachedTVClass implements ThirdPartyTVLib isprivate field service: ThirdPartyTVLibprivate field listCache, videoCachefield needResetconstructor CachedTVClass(service: ThirdPartyTVLib) isthis.service = servicemethod listVideos() isif (listCache == null || needReset)listCache = service.listVideos()return listCachemethod getVideoInfo(id) isif (videoCache == null || needReset)videoCache = service.getVideoInfo(id)return videoCachemethod downloadVideo(id) isif (!downloadExists(id) || needReset)service.downloadVideo(id)// 之前直接与服务对象交互的 GUI 类不需要改变,前提是它仅通过接口与服务对
// 象交互。我们可以安全地传递一个代理对象来代替真实服务对象,因为它们都实
// 现了相同的接口。
class TVManager isprotected field service: ThirdPartyTVLibconstructor TVManager(service: ThirdPartyTVLib) isthis.service = servicemethod renderVideoPage(id) isinfo = service.getVideoInfo(id)// 渲染视频页面。method renderListPanel() islist = service.listVideos()// 渲染视频缩略图列表。method reactOnUserInput() isrenderVideoPage()renderListPanel()// 程序可在运行时对代理进行配置。
class Application ismethod init() isaTVService = new ThirdPartyTVClass()aTVProxy = new CachedTVClass(aTVService)manager = new TVManager(aTVProxy)manager.reactOnUserInput()

 代理模式适合应用场景

使用代理模式的方式多种多样, 我们来看看最常见的几种。

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

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

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

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

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

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

 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。

 代理可以在向服务传递请求前进行记录。

 缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。

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

 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。

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

代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

 实现方式

  1. 如果没有现成的服务接口, 你就需要创建一个接口来实现代理和服务对象的可交换性。 从服务类中抽取接口并非总是可行的, 因为你需要对服务的所有客户端进行修改, 让它们使用接口。 备选计划是将代理作为服务类的子类, 这样代理就能继承服务的所有接口了。

  2. 创建代理类, 其中必须包含一个存储指向服务的引用的成员变量。 通常情况下, 代理负责创建服务并对其整个生命周期进行管理。 在一些特殊情况下, 客户端会通过构造函数将服务传递给代理。

  3. 根据需求实现代理方法。 在大部分情况下, 代理在完成一些任务后应将工作委派给服务对象。

  4. 可以考虑新建一个构建方法来判断客户端可获取的是代理还是实际服务。 你可以在代理类中创建一个简单的静态方法, 也可以创建一个完整的工厂方法。

  5. 可以考虑为服务对象实现延迟初始化。

 代理模式优缺点

  •  你可以在客户端毫无察觉的情况下控制服务对象。
  •  如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
  •  即使服务对象还未准备好或不存在, 代理也可以正常工作。
  •  开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。
  •  代码可能会变得复杂, 因为需要新建许多类。
  •  服务响应可能会延迟。

 与其他模式的关系

  • ​ 适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。 ​

 代码示例

#include <iostream>
/*** The Subject interface declares common operations for both RealSubject and the* Proxy. As long as the client works with RealSubject using this interface,* you'll be able to pass it a proxy instead of a real subject.*/
class Subject {public:virtual void Request() const = 0;
};
/*** The RealSubject contains some core business logic. Usually, RealSubjects are* capable of doing some useful work which may also be very slow or sensitive -* e.g. correcting input data. A Proxy can solve these issues without any* changes to the RealSubject's code.*/
class RealSubject : public Subject {public:void Request() const override {std::cout << "RealSubject: Handling request.\n";}
};
/*** The Proxy has an interface identical to the RealSubject.*/
class Proxy : public Subject {/*** @var RealSubject*/private:RealSubject *real_subject_;bool CheckAccess() const {// Some real checks should go here.std::cout << "Proxy: Checking access prior to firing a real request.\n";return true;}void LogAccess() const {std::cout << "Proxy: Logging the time of request.\n";}/*** The Proxy maintains a reference to an object of the RealSubject class. It* can be either lazy-loaded or passed to the Proxy by the client.*/public:Proxy(RealSubject *real_subject) : real_subject_(new RealSubject(*real_subject)) {}~Proxy() {delete real_subject_;}/*** The most common applications of the Proxy pattern are lazy loading,* caching, controlling the access, logging, etc. A Proxy can perform one of* these things and then, depending on the result, pass the execution to the* same method in a linked RealSubject object.*/void Request() const override {if (this->CheckAccess()) {this->real_subject_->Request();this->LogAccess();}}
};
/*** The client code is supposed to work with all objects (both subjects and* proxies) via the Subject interface in order to support both real subjects and* proxies. In real life, however, clients mostly work with their real subjects* directly. In this case, to implement the pattern more easily, you can extend* your proxy from the real subject's class.*/
void ClientCode(const Subject &subject) {// ...subject.Request();// ...
}int main() {std::cout << "Client: Executing the client code with a real subject:\n";RealSubject *real_subject = new RealSubject;ClientCode(*real_subject);std::cout << "\n";std::cout << "Client: Executing the same client code with a proxy:\n";Proxy *proxy = new Proxy(real_subject);ClientCode(*proxy);delete real_subject;delete proxy;return 0;
}

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

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

相关文章

Ubuntu 常用命令之 mkfs 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 mkfs 是在 Linux 和其他 Unix-like 系统中用于创建文件系统的命令。在 Ubuntu 系统中&#xff0c;mkfs 命令也是用于创建文件系统的。mkfs 是一个包装器&#xff0c;它会根据用户指定的文件系统类型调用相应的程序。 mkfs 命令的…

Guava的TypeToken在泛型编程中的应用

第1章&#xff1a;引言 在Java世界里&#xff0c;泛型是个相当棒的概念&#xff0c;能让代码更加灵活和类型安全。但是&#xff0c;泛型也带来了一些挑战&#xff0c;特别是当涉及到类型擦除时。这就是TypeToken大显身手的时候&#xff01; 作为Java程序员的咱们&#xff0c;…

【ITK库学习】使用itk库进行图像配准:内插器(插值)

目录 1、itkNearestNeighborInterpolateImageFunction 最近点插值2、itkLinearInterpolateImageFunction 线性插值3、itkBSplineInterpolateImageFunction B样条插值4、itkWindowedSincInterpolateImageFunction 窗口化Sinc插值5、itkRayCastInterpolateImageFunction 投射插值…

Mac OS 13+,Apple Silicon,删除OBS虚拟摄像头(virtual camera),

原文链接: https://www.reddit.com/r/MacOS/comments/142cv OBS为了捕获摄像头视频,将虚拟摄像头插件内置为系统插件了.如下 直接删除没有权限的,要删除他,在mac os 13以后,需要关闭先关闭苹果系统的完整性保护(SIP) Apple 芯片(M1,....)的恢复模式分为两种,回退恢复模式,和…

MySQL数据库 视图

目录 视图概述 语法 检查选项 视图的更新 视图作用 案例 视图概述 视图(View)是一种虚拟存在的表。视图中的数据并不在数据库中实际存在&#xff0c;行和列数据来自定义视图的查询中使用的表&#xff0c;并且是在使用视图时动态生成的。 通俗的讲&#xff0c;视图只保存…

K8S 日志方案

目录 一、统一日志管理的整体方案 1、基础日志 2、Node级别的日志 3、集群级别的日志架构 二、安装统一日志管理组件 1、 部署Elasticsearch 2、部署Fluentd 3、部署Kibana 三、日志数据展示 一、统一日志管理的整体方案 通过应用和系统日志可以了解Kubernetes集群内…

【EasyExcel实践】万能导出,一个接口导出多张表以及任意字段(可指定字段顺序)-简化升级版

文章目录 前言正文一、项目简介二、核心代码2.1 pom.xml 依赖配置2.2 ExcelHeadMapFactory2.3 ExcelDataLinkedHashMap2.4 自定义注解 ExcelExportBean2.5 自定义注解 ExcelColumnTitle2.6 建造器接口 Builder2.7 表格工具类 ExcelUtils2.8 GsonUtil2.9 模版类 ExportDynamicCo…

概率中的50个具有挑战性的问题[02/50]:连续获胜

一、说明 我最近对与概率有关的问题产生了兴趣。我偶然读到了弗雷德里克莫斯特勒&#xff08;Frederick Mosteller&#xff09;的《概率论中的五十个具有挑战性的问题与解决方案》&#xff08;Fifty Challenge Problems in Probability with Solutions&#xff09;一书。我认为…

HarmonyOS构建第一个ArkTS应用(FA模型)

构建第一个ArkTS应用&#xff08;FA模型&#xff09; 创建ArkTS工程 若首次打开DevEco Studio&#xff0c;请点击Create Project创建工程。如果已经打开了一个工程&#xff0c;请在菜单栏选择File > New > Create Project来创建一个新工程。 选择Application应用开发&a…

十三、W5100S/W5500+RP2040之MicroPython开发<MQTT新版OneNET示例>

文章目录 1. 前言2. 平台操作流程3. WIZnet以太网芯片4. 示例讲解以及使用4.1 程序流程图4.2 测试准备4.3 连接方式4.4 相关代码4.5 烧录验证 5. 注意事项6. 相关链接 1. 前言 在这个智能硬件和物联网时代&#xff0c;MicroPython和树莓派PICO正以其独特的优势引领着嵌入式开发…

【SpringBoot】之Security集成使用(入门级)

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《SpringBoot开发之Security系列》。&#x1f3af…

【SpringBoot】之Security进阶使用

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是君易--鑨&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的博客专栏《SpringBoot开发之Security系列》。&#x1f3af…

k8s启动docker容器Error: Could not find or load main class ${start-class}报错

前行提要&#xff1a; 今天部署采集点服务&#xff08;docker项目&#xff09;发现报这个错误。 提出假设&#xff1a; 1&#xff0c;配置文件错误&#xff08;工程需要配置的东西比较多&#xff09; 之后开始一一排查&#xff0c;发现配置有问题&#xff0c;但是不是这个错误…

modbus_tcp的实现 through python.

0.引言 当前科技似乎处于加速发展期&#xff0c;各个模块都在快速迭代&#xff0c;迭代的速度会让既有的一些经验产生问题&#xff0c;在用python实现modbus_tcp协议时&#xff0c;网上流传的一些代码中import语句会出现问题。导致pymodbus模块用起来很不好用。 这个原因出在…

【ps】常见工具说明

1&#xff1a;污点修复工具 》内容识别 去除脸部珍珠 》创建纹理 2&#xff1a;修复画笔工具 按住ALT选择一处你想移动的地方 3&#xff1a;修补工具 》内容识别 选择一个区域 将区域内的东西移动到 另一处皮肤上

C语言之字符串

目录 字符串字面量 ​编辑 字符串字面量的长度 ◆具有静态生命周期 ◆对于同一个字符串字面量的处理方式依赖于编译器 字符串 字符数组的初始化赋值 空字符串 字符串的读取 在前面的学习中就会发现&#xff0c;仅仅能用一个字符表示的事物少之又少&#xff0c;对于地…

日志服务 SLS 深度解析:拥抱云原生和 AI,基于 SLS 的可观测分析创新

云布道师 10 月 31 日&#xff0c;杭州云栖大会上&#xff0c;日志服务 SLS 研发负责人简志和产品经理孟威等人发表了《日志服务 SLS 深度解析&#xff1a;拥抱云原生和 AI&#xff0c;基于 SLS 的可观测分析创新》的主题演讲&#xff0c;对阿里云日志服务 SLS 产品服务创新以…

2023/12/21作业

思维导图 代码 .text .global _start _start: 灯1 gpio时钟使能 [4]->1 0x5000A28 LDR R0,0x50000A28 指定寄存器地址 LDR R1,[R0]将寄存器取出放到R1 ORR R1,R1,#(0x1<<4)将第四位设置为1 STR R1,[R0]读取R0寄存器到R1 PE…

Ubuntu 常用命令之 reboot 命令用法介绍

&#x1f4d1;Linux/Ubuntu 常用命令归类整理 reboot命令在Ubuntu系统中用于重新启动系统。这个命令通常需要管理员权限才能执行。 reboot命令的参数如下 -f 或 --force&#xff1a;强制重启&#xff0c;不调用shutdown -r进行友好重启。-p 或 --poweroff&#xff1a;在重启…

arm和x86架构服务器拉取arm64架构的docker镜像

dockerhub提供的镜像部分支持arm64架构 Docker arm架构服务器拉取docker镜像&#xff0c;默认是arm架构 # docker pull centos Using default tag: latest latest: Pulling from library/centos 52f9ef134af7: Pull complete Digest: sha256:a27fd8080b517143cbbbab9dfb7c8…