署理形式

亦称: Proxy

署理形式是一种结构型规划形式, 让你能够供给目标的替代品或其占位符。 署理操控着关于原目标的拜访, 并答应在将恳求提交给目标前后进行一些处理。

2023 跟我一起学设计模式:  代理模式

问题

为什么要操控关于某个目标的拜访呢? 举个例子: 有这样一个耗费很多系统资源的巨型目标, 你仅仅偶然需求运用它, 并非总是需求。

2023 跟我一起学设计模式:  代理模式

数据库查询有可能会十分缓慢。

你能够完成推迟初始化: 在实践有需求时再创立该目标。 目标的全部客户端都要履行推迟初始代码。 不幸的是, 这很可能会带来很多重复代码。

在抱负状况下, 咱们期望将代码直接放入目标的类中, 但这并非总是能完成: 比方类可能是第三方封闭库的一部分。

解决方案

署理形式建议新建一个与原服务目标接口相同的署理类, 然后更新应用以将署理目标传递给全部原始目标客户端。 署理类接收到客户端恳求后会创立实践的服务目标, 并将全部作业委派给它。

2023 跟我一起学设计模式:  代理模式

署理将自己伪装成数据库目标, 可在客户端或实践数据库目标不知情的状况下处理推迟初始化和缓存查询成果的作业。

这有什么优点呢? 假如需求在类的主要事务逻辑前后履行一些作业, 你无需修正类就能完成这项作业。 由于署理完成的接口与原类相同, 因此你可将其传递给任何一个运用实践服务目标的客户端。

实在国际类比

2023 跟我一起学设计模式:  代理模式

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

信用卡是银行账户的署理, 银行账户则是一大捆现金的署理。 它们都完成了相同的接口, 均可用于进行付出。 顾客会十分满意, 由于不必随身携带很多现金; 商铺老板相同会十分高兴, 由于交易收入能以电子化的方式进入商铺的银行账户中, 无需担心存款时出现现金丢掉或被抢劫的状况。

署理形式结构

2023 跟我一起学设计模式:  代理模式

  1. 服务接口 (Service Interface) 声明晰服务接口。 署理有必要遵循该接口才干伪装成服务目标。

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

  3. 署理 (Proxy) 类包括一个指向服务目标的引证成员变量。 署理完成其任务 (例如推迟初始化、 记载日志、 拜访操控和缓存等) 后会将恳求传递给服务目标。

    通常状况下, 署理会对其服务目标的整个生命周期进行办理。

  4. 客户端 (Client) 能经过同一接口与服务或署理进行交互, 所以你可在全部需求服务目标的代码中运用署理。

伪代码

本例演示怎么运用署理形式在第三方腾讯视频 (TencentVideo, 代码示例中记为 TV) 程序库中增加推迟初始化和缓存。

2023 跟我一起学设计模式:  代理模式

运用署理缓冲服务成果。

程序库供给了视频下载类。 但是该类的功率十分低。 假如客户端程序多次恳求同一视频, 程序库会重复下载该视频, 而不会将首次下载的文件缓存下来复用。

署理类完成和原下载器相同的接口, 并将全部作业委派给原下载器。 不过, 署理类会保存全部的文件下载记载, 假如程序多次恳求同一文件, 它会返回缓存的文件。

// 长途服务接口。
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. 能够考虑为服务目标完成推迟初始化。

署理形式优缺点

  • 你能够在客户端毫无察觉的状况下操控服务目标。

  • 假如客户端对服务目标的生命周期没有特殊要求, 你能够对生命周期进行办理。

  • 即便服务目标还未准备好或不存在, 署理也能够正常作业。

  • 开闭原则。 你能够在不对服务或客户端做出修正的状况下创立新署理。

  • 代码可能会变得复杂, 由于需求新建许多类。

  • 服务呼应可能会推迟。

代码示例

  • Go 署理形式讲解和代码示例 – 掘金 ()