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

news2024/9/29 21:32:15

引言

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

问题

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

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

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

解决方案

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

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

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

真实世界类比

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

代理模式结构

伪代码

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

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

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

// 远程服务接口。
interface ThirdPartyTVLib is
    method listVideos()
    method getVideoInfo(id)
    method downloadVideo(id)

// 服务连接器的具体实现。该类的方法可以向腾讯视频请求信息。请求速度取决于
// 用户和腾讯视频的互联网连接情况。如果同时发送大量请求,即使所请求的信息
// 一模一样,程序的速度依然会减慢。
class ThirdPartyTVClass implements ThirdPartyTVLib is
    method listVideos() is
        // 向腾讯视频发送一个 API 请求。

    method getVideoInfo(id) is
        // 获取某个视频的元数据。

    method downloadVideo(id) is
        // 从腾讯视频下载一个视频文件。

// 为了节省网络带宽,我们可以将请求结果缓存下来并保存一段时间。但你可能无
// 法直接将这些代码放入服务类中。比如该类可能是第三方程序库的一部分或其签
// 名是`final(最终)`。因此我们会在一个实现了服务类接口的新代理类中放入
// 缓存代码。当代理类接收到真实请求后,才会将其委派给服务对象。
class CachedTVClass implements ThirdPartyTVLib is
    private field service: ThirdPartyTVLib
    private field listCache, videoCache
    field needReset

    constructor CachedTVClass(service: ThirdPartyTVLib) is
        this.service = service

    method listVideos() is
        if (listCache == null || needReset)
            listCache = service.listVideos()
        return listCache

    method getVideoInfo(id) is
        if (videoCache == null || needReset)
            videoCache = service.getVideoInfo(id)
        return videoCache

    method downloadVideo(id) is
        if (!downloadExists(id) || needReset)
            service.downloadVideo(id)

// 之前直接与服务对象交互的 GUI 类不需要改变,前提是它仅通过接口与服务对
// 象交互。我们可以安全地传递一个代理对象来代替真实服务对象,因为它们都实
// 现了相同的接口。
class TVManager is
    protected field service: ThirdPartyTVLib

    constructor TVManager(service: ThirdPartyTVLib) is
        this.service = service

    method renderVideoPage(id) is
        info = service.getVideoInfo(id)
        // 渲染视频页面。

    method renderListPanel() is
        list = service.listVideos()
        // 渲染视频缩略图列表。

    method reactOnUserInput() is
        renderVideoPage()
        renderListPanel()

// 程序可在运行时对代理进行配置。
class Application is
    method init() is
        aTVService = new ThirdPartyTVClass()
        aTVProxy = new CachedTVClass(aTVService)
        manager = new TVManager(aTVProxy)
        manager.reactOnUserInput()

代理模式适合应用场景

  • 使用代理模式的方式多种多样, 我们来看看最常见的几种。
  •  延迟初始化 (虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。
  •  你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。
  •  访问控制 (保护代理)。 如果你只希望特定客户端使用服务对象, 这里的对象可以是操作系统中非常重要的部分, 而客户端则是各种已启动的程序 (包括恶意程序), 此时可使用代理模式。
  •  代理可仅在客户端凭据满足要求时将请求传递给服务对象。
  •  本地执行远程服务 (远程代理)。 适用于服务对象位于远程服务器上的情形。
  •  在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。
  •  记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。
  •  代理可以在向服务传递请求前进行记录。
  •  缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。
  •  代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。
  •  智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。
  •  代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。
  • 代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

 实现方式

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

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

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

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

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

 代理模式优缺点

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

 与其他模式的关系

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

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

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

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

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

相关文章

10.鸿蒙应用程序app创建第一个程序Helloworld

鸿蒙应用程序开发app_hap开发环境搭建 1.打开DevEco 2.创建项目 3.选择Empty Ability 4. 选择API6,支持java开发 5.点击Finish 6.启动本地模拟器参考方法 7.启动成功 8.运行程序 9.运行成功 其它文章点击专栏

spring之面向切面:AOP(2)

学习的最大理由是想摆脱平庸,早一天就多一份人生的精彩;迟一天就多一天平庸的困扰。各位小伙伴,如果您: 想系统/深入学习某技术知识点… 一个人摸索学习很难坚持,想组团高效学习… 想写博客但无从下手,急需…

Python Opencv实践 - 手部跟踪

使用mediapipe库做手部的实时跟踪,关于mediapipe的介绍,请自行百度。 mediapipe做手部检测的资料,可以参考这里: MediaPipe Hands: On-device Real-time Hand Tracking 论文阅读笔记 - 知乎论文地址: https://arxiv.org/abs/2006…

不用再找了,这是大模型实践最全的总结

随着ChatGPT的迅速出圈,加速了大模型时代的变革。对于以Transformer、MOE结构为代表的大模型来说,传统的单机单卡训练模式肯定不能满足上千(万)亿级参数的模型训练,这时候我们就需要解决内存墙和通信墙等一系列问题&am…

ATKXCOM串口助手接受中文字符乱码问题

中文乱码大多是编码格式问题,如心知天气API返回的数据编码格式为UTF-8格式,同理串口调试助手需要更改为对应的编码格式,正点原子的串口调试助手具有多种编码格式可以更改。 PS:点击左下角设置图标即可设置

手撕分布式缓存---HTTP Client搭建

经过上个章节的学习,我们已经实现了一致性哈希算法,这个算法保证我们可以在节点发生变动时,最少的key请求受到影响,并返回这个节点的名称;这很大程度上避免了哈希雪崩和哈希穿透的问题。这个章节我们要基于此实现完整的…

tensorflow入门

一、怎样入手TensorFlow TensorFlow是一个用于机器学习和深度学习的开源框架,它提供了一种灵活的方式来构建和训练神经网络模型。以下是一些TensorFlow框架入门的建议: 学习Python语言:TensorFlow主要使用Python语言进行开发,因此…

自动驾驶技术入门平台分享:百度Apollo开放平台9.0全方位升级

目录 平台全方位的升级 全新的架构 工具服务 应用软件(场景应用) 软件核心 硬件设备 更强的算法能力 9.0版本算法升级总结 更易用的工程框架 Apollo开放平台9.0版本的技术升级为开发者提供了许多显著的好处,特别是对于深度开发需求…

【Java】工业园区高精准UWB定位系统源码

UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。UWB定位系统依托在移动通信,雷达,微波电路,云计算与大数据…

Vim命令大全(超详细,适合反复阅读学习)

Vim命令大全 Vim简介Vim中的模式光标移动命令滚屏与跳转文本插入操作文本删除操作文本复制、剪切与粘贴文本的修改与替换文本的查找与替换撤销修改、重做与保存编辑多个文件标签页与折叠栏多窗口操作总结 Vim是一款文本编辑器,是Vi编辑器的增强版。Vim的特点是快速、…

Node.js使用Express框架写服务端接口时,如何将接口拆分到不同文件中

项目目录结构说明: node.js连接mysql数据库步骤可参考:Node.js 连接 MySQL | 菜鸟教程 1、拆分之前的写法,未区分模块,所有接口api都写在了入口文件app.js中; 需求:想要将接口api拆分成根据不同的业务模块…

导致OpenAI内乱的罪魁祸首,背后的技术是什么?

前几天围绕Sam 和 Greg和OpenAI board之间的爱恨情仇,我觉得比乡村爱情15还有意思,也达到了美剧多年未有的高度,反转反转再反转。 围绕争端的根本原因,那也是众说纷纭,不过其实有一条新闻我觉得挺值得玩味的,也是我所相信的,就是Sam在OpenAI day上发布了一个叫GP…

C++设计模式——装饰器模式

装饰器设计模式 概念应用场景优点示例示例一代码实现运行结果 示例二代码实现运行结果 示例三实现代码运行结果 总结 概念 装饰器设计模式,是C设计模式中的一种。它是一种结构型设计模式,允许向现有对象动态地添加新功能,同时又不改变其结构…

使用Python将OSS文件免费下载到本地:第一步 列举OSS文件

大家好,我是水滴~~ 本文将介绍了使用的知识点、以及列举OSS文件的代码、并对该代码进行详细解析、最后给出部署方案,希望能对你有所帮助! 《Python入门核心技术》专栏总目录・点这里 文章目录 1. 本文知识点1.1 datetime 模块1.2 OSS Python…

PyQt5连接mysql失败解决

一:背景 最近研究一个项目,里面用的Pyqt5编写的桌面应用,跑了下源码发现连接数据库那块出来问题,最终调试发现里面用的QtSql去连接mysql提示驱动找不到。 具体报错信息如下: Could not parse stylesheet of object …

使用VBA字典,进行数据分类汇总

使用VBA字典,进行数据分类汇总 VBA的字典共有两列,第一列是key,不允许有重复的元素;第二列是item,也就是key对应的值,item的值是可以有重复的值的。 字典的主要操作有读和写。 写操作 d(key)item&#…

直流电、交流电和发电机、接地、变压器

直流电 此节内容主要摘录自:图文详解直流电与直流电路基本知识 直流电是指电流方向不随时间作周期性变化,由正极流向负极,但电流的大小可能会变化的电流。直流电可以分为稳定(恒定)直流和脉动直流两种,如下…

迪文屏开发保姆级教程—背景图ICL文件生成

本篇文章主要介绍了在DGBUS平台上生成页面背景图片库,32xx.ICL文件的方法。 文章目录 一、前言 开发环境 二、具体步骤 1.打开软件 2.选定参数 3.导入背景图片 4.然后点击生成,​编辑 三、容易踩得坑 一、前言 本篇文章主要介绍了在DGBUS平台上生…

SQLturning:定位连续值范围起点和终点

在上一篇blog说到,如何去优化查询连续值范围,没看过的朋友,上篇blog链接[在此]。(https://blog.csdn.net/weixin_42575078/article/details/135067645?spm1001.2014.3001.5501) 那么今天来说说怎么将连续的数据合并,然后返回合并…

红米k40刷机澎湃OS

红米k40线刷澎湃OS,MIUI→HyperOS 1.0.23.12.9 博主自己也是个小白 rom包免费获取与体验请关注公众号:YouLinw的ROM日常 资料备份 使用小米自带的打包备份工具,将文件备份到电脑上 或使用小米自带的云服务功能,我开了会员。换了…