代理设计模式

文章正文
发布时间:2024-09-25 11:38

/ 设计模式 / 结构型模式

代理模式

亦称:Proxy

意图

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

代理设计模式

问题

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

代理模式解决的问题

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

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

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

解决方案

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

代理模式的解决方案

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

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

真实世界类比

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

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

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

代理模式结构

代理设计模式的结构

代理设计模式的结构

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

服务Service类提供了一些实用的业务逻辑

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

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

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

伪代码

本例演示如何使用代理模式在第三方腾讯视频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()

代理模式适合应用场景

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

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

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

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

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

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

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

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

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

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

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

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

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

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

实现方式

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

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

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

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

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

代理模式优缺点

你可以在客户端毫无察觉的情况下控制服务对象

如果客户端对服务对象的生命周期没有特殊要求你可以对生命周期进行管理

即使服务对象还未准备好或不存在代理也可以正常工作

你可以在不对服务或客户端做出修改的情况下创建新代理

代码可能会变得复杂因为需要新建许多类

服务响应可能会延迟

与其他模式的关系

代码示例

C++ 代理模式讲解和代码示例

Go 代理模式讲解和代码示例

Java 代理模式讲解和代码示例

PHP 代理模式讲解和代码示例

Python 代理模式讲解和代码示例

Ruby 代理模式讲解和代码示例

Rust 代理模式讲解和代码示例

Swift 代理模式讲解和代码示例

TypeScript 代理模式讲解和代码示例

支持我们的免费网站并获得电子书!

22 个设计模式和 8 条原则的详细讲解。

382 页精心编排、易于阅读且没有术语的内容。

225 幅有助于理解的简明图表。

11 种编程语言的代码示例档案。

支持所有的设备:PDF/EPUB/MOBI/KFX 格式。

首页
评论
分享
Top