设计模式——代理模式(结构型)

引言

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

问题

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

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

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

解决方案

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

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

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

真实世界类比

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

代理模式结构

伪代码

本例演示如何使用代理模式在第三方腾讯视频 (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. 可以考虑为服务对象实现延迟初始化。

 代理模式优缺点

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

 与其他模式的关系

  • 适配器模式能为被封装对象提供不同的接口, 代理模式能为对象提供相同的接口, 装饰模式则能为对象提供加强的接口。

  • 外观模式与代理的相似之处在于它们都缓存了一个复杂实体并自行对其进行初始化。 代理与其服务对象遵循同一接口, 使得自己和服务对象可以互换, 在这一点上它与外观不同。

  • 装饰和代理有着相似的结构, 但是其意图却非常不同。 这两个模式的构建都基于组合原则, 也就是说一个对象应该将部分工作委派给另一个对象。 两者之间的不同之处在于代理通常自行管理其服务对象的生命周期, 而装饰的生成则总是由客户端进行控制。 

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

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

相关文章

为什么选择计算机?大数据时代学习计算机的价值探讨

还记得当初自己为什么选择计算机? 计算机是在90年代兴起的专业,那时候的年轻人有驾照、懂外语、懂计算机是很时髦的事情! 当初你问我为什么选择计算机,我笑着回答:“因为我梦想成为神奇的码农!我想像编织魔法一样编写程序,创造出炫酷的虚拟世界!”谁知道,我刚入门的…

java内置的数据结构

Java语言提供了许多内置的数据结构,包括: 1. 数组(Array):数组是最基本的数据结构之一,它是一个有序的元素集合,每个元素都有一个对应的索引。在Java中,数组可以通过声明和初始化来创…

stm32F4——蜂鸣器与按键的实例使用

stm32F4——蜂鸣器与按键的实例使用 蜂鸣器和按键的实质都是GPIO的使用,所以基本原理就不介绍啦,基本寄存器其实都是GPIO的高低电平的赋值,可以参考stm32——LEDGPIO的详细介绍 使用的开发板是华清远见的stm32F407VET6,都是M4的内…

AtCoder Beginner Contest 332 G. Not Too Many Balls(最大流转最小割 dp)

题目 n(n<500)种球&#xff0c;第i种有ai(0<ai<1e12)个球&#xff0c; m(m<5e5)个盒子&#xff0c;第j个能放bj(0<bj<1e12)个球 特别地&#xff0c;第j个盒子最多能放i*j个第i种球 求m个盒子能放的最多的球的总数 思路来源 官方题解 题解 显然是一个最…

STM32——时钟树与滴答计时器

STM32——时钟树与滴答计时器 使用的开发板为stm32F407VET6的芯片,主要介绍stm32的时钟树与滴答计时器的一些理论和一个自己编写的delay函数。 时钟树的结构图可以在STM32F4xx中文参考手册.pdf中的时钟这块找到。而滴答计时器是内核资源&#xff0c;需要到Cortex M3与M4权威指南…

链路聚合 (hcia)

原理 采用链路聚合技术可以在不进行硬件升级的条件下&#xff0c;通过将多个物理接口捆绑为一个逻辑接 口&#xff0c;达到增加链路带宽的目的。在实现增大带宽目的的同时&#xff0c;链路聚合采用备份链路的机制&#xff0c; 可以有效的提高设备之间链路的可靠性 &#x…

Error message “error:0308010C:digital envelope routines::unsupported“ 解决方案

大家好,我是爱编程的喵喵。双985硕士毕业,现担任全栈工程师一职,热衷于将数据思维应用到工作与生活中。从事机器学习以及相关的前后端开发工作。曾在阿里云、科大讯飞、CCF等比赛获得多次Top名次。现为CSDN博客专家、人工智能领域优质创作者。喜欢通过博客创作的方式对所学的…

Java_Lambda表达式JDK8新特性(方法引用)

一、Lambda表达式 接下来&#xff0c;我们学习一个JDK8新增的一种语法形式&#xff0c;叫做Lambda表达式。作用&#xff1a;用于简化匿名内部类代码的书写。 1.1 Lambda表达式基本使用 怎么去简化呢&#xff1f;Lamdba是有特有的格式的&#xff0c;按照下面的格式来编写Lamd…

认识loader和plugin

在 webpack 中&#xff0c;专注于处理 webpack 在编译过程中的某个特定的任务的功能模块&#xff0c;可以称为插件。它和 loader 有以下区别&#xff1a; 1loader 是一个转换器&#xff0c;将 A 文件进行编译成 B 文件&#xff0c;比如&#xff1a;将 A.less 转换为 A.css&…

2019年第八届数学建模国际赛小美赛A题放射性产生的热量解题全过程文档及程序

2019年第八届数学建模国际赛小美赛 A题 放射性产生的热量 原题再现&#xff1a; 假设我们把一块半衰期很长的放射性物质做成一个特定的形状。在这种材料中&#xff0c;原子核在衰变时会以随机的方向释放质子。我们假设携带质子的能量是一个常数。质子在穿过致密物质时&#x…

关于脑区的划分方法及一些模板说明

关于脑区的划分方法及一些模板说明 前言脑区划分方法的种类一些标准的脑区划分模板参考文献 前言 原创文章&#xff0c;未经同意请勿转载 Status: Completed Author: xioabai_Ry Time to Note: March 23, 2022 这里主要记录之前自己调研的有关脑区的划分方法及一些标准的脑区…

Web前端-HTML(表格与表单)

文章目录 1.表格与表单1.1 概述 2.表格 table2.1 表格概述2.2. 创建表格2.3 表格属性2.4. 表头单元格标签th2.5 表格标题caption&#xff08;了解&#xff09;2.6 合并单元格(难点)2.7 总结表格 3. 表单标签(重点)3.1 概述3.2 form表单3.3 input 控件(重点)type 属性value属性值…

机器学习算法---聚类

类别内容导航机器学习机器学习算法应用场景与评价指标机器学习算法—分类机器学习算法—回归机器学习算法—聚类机器学习算法—异常检测机器学习算法—时间序列数据可视化数据可视化—折线图数据可视化—箱线图数据可视化—柱状图数据可视化—饼图、环形图、雷达图统计学检验箱…

【Redis】AOF 基础

因为 Redis AOF 的实现有些绕, 就分成 2 篇进行分析, 本篇主要是介绍一下 AOF 的一些特性和依赖的其他函数的逻辑,为下一篇 (Redis AOF 源码) 源码分析做一些铺垫。 AOF 全称: Append Only File, 是 Redis 提供了一种数据保存模式, Redis 默认不开启。 AOF 采用日志的形式来记…

写好ChatGPT提示词原则之:清晰且具体(clear specific)

ChatGPT 的优势在于它允许用户跨越机器学习和深度学习的复杂门槛&#xff0c;直接利用已经训练好的模型。然而&#xff0c;即便是这些先进的大型语言模型也面临着上下文理解和模型固有局限性的挑战。为了最大化这些大型语言模型&#xff08;LLM&#xff09;的潜力&#xff0c;关…

.NET core 搭建一个跨平台的 Web Service

以前搭建的webservice 都是基于.NET fromwork的&#xff0c;我们知道.NET fromwork是非跨平台的&#xff0c;只能部署在iis上&#xff0c;今天教大家用.NET core搭建一个可跨平台的Web Service 新建一个.net core空项目 给项目起一个名字 选一个.net框架&#xff0c;我这里选…

电脑开机出现:CLIENT MAD ADDR (网卡启动系统)的解决办法

文章目录 前言步骤1、确定情况2、对症下药——关闭网卡启动 补充1、关于BIOS2、关于PXE 前言 最近给旧电脑重装系统安了下开发环境和常用软件啥的&#xff0c;之前还好好启动的电脑&#xff0c;开机突然需要额外加载一个页面&#xff0c;虽然最后正常启动了不影响使用&#xf…

60.Sentinel源码分析

Sentinel源码分析 1.Sentinel的基本概念 Sentinel实现限流、隔离、降级、熔断等功能&#xff0c;本质要做的就是两件事情&#xff1a; 统计数据&#xff1a;统计某个资源的访问数据&#xff08;QPS、RT等信息&#xff09; 规则判断&#xff1a;判断限流规则、隔离规则、降级规…

亚马逊云科技 re:Invent 大会 - S3 对象存储华丽升级

亚马逊云科技 re:Invent 大会 - S3 对象存储华丽升级 本篇文章授权活动官方亚马逊云科技文章转发、改写权&#xff0c;包括不限于在 亚马逊云科技开发者社区, 知乎&#xff0c;自媒体平台&#xff0c;第三方开发者媒体等亚马逊云科技官方渠道。 文章目录 亚马逊云科技 re:Inv…

Flink系列之:大状态与 Checkpoint 调优

Flink系列之&#xff1a;大状态与 Checkpoint 调优 一、概述二、监控状态和 Checkpoints三、Checkpoint 调优四、RocksDB 调优五、增量 Checkpoint六、RocksDB 或 JVM 堆中的计时器七、RocksDB 内存调优八、容量规划九、压缩十、Task 本地恢复十一、主要&#xff08;分布式存储…