C++设计模式(15)——代理模式

news2024/12/27 12:21:40

亦称: Proxy

意图

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

问题

为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个消耗大量系统资源的巨型对象, 你只是偶尔需要使用它, 并非总是需要。
在这里插入图片描述
数据库查询有可能会非常缓慢。

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

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

解决方案

代理模式建议新建一个与原服务对象接口相同的代理类, 然后更新应用以将代理对象传递给所有原始对象客户端。 代理类接收到客户端请求后会创建实际的服务对象, 并将所有工作委派给它。
在这里插入图片描述
代理将自己伪装成数据库对象, 可在客户端或实际数据库对象不知情的情况下处理延迟初始化和缓存查询结果的工作。

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

真实世界类比

在这里插入图片描述
信用卡和现金在支付过程中的用处相同。

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

代理模式结构

在这里插入图片描述
1、服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。

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

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

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

4、客户端 (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()

代理模式适合应用场景

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

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

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

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

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

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

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

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

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

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

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

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

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

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

实现方式

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

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

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

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

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

代理模式优缺点

在这里插入图片描述

与其他模式的关系

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

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

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

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

代理是一种结构型设计模式, 让你能提供真实服务对象的替代品给客户端使用。 代理接收客户端的请求并进行一些处理 (访问控制和缓存等), 然后再将请求传递给服务对象。

代理对象拥有和服务对象相同的接口, 这使得当其被传递给客户端时可与真实对象互换。

使用示例: 尽管代理模式在绝大多数 C++ 程序中并不常见, 但它在一些特殊情况下仍然非常方便。 当你希望在无需修改客户代码的前提下于已有类的对象上增加额外行为时, 该模式是无可替代的。

识别方法: 代理模式会将所有实际工作委派给一些其他对象。 除非代理是某个服务的子类, 否则每个代理方法最后都应该引用一个服务对象。

概念示例

本例说明了代理设计模式的结构并重点回答了下面的问题:

它由哪些类组成?
这些类扮演了哪些角色?
模式中的各个元素会以何种方式相互关联?

main.cc: 概念示例

#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;
}

Output.txt: 执行结果

Client: Executing the client code with a real subject:
RealSubject: Handling request.

Client: Executing the same client code with a proxy:
Proxy: Checking access prior to firing a real request.
RealSubject: Handling request.
Proxy: Logging the time of request.

来源:https://refactoringguru.cn/design-patterns/proxy
仅供学习,非商业用途,侵删

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

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

相关文章

分享117个HTML婚纱模板,总有一款适合您

分享117个HTML婚纱模板&#xff0c;总有一款适合您 117个HTML婚纱模板下载链接&#xff1a;https://pan.baidu.com/s/1cC3I5cfh91-KmQj4nfSoPA?pwd9hod 提取码&#xff1a;9hod Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 import os import shutil import …

懂九转大肠的微软New Bing 内测申请教程

最近微软的New Bing开放内测了&#xff0c;网上已经有拿到内测资格的大佬们对比了ChatGPT和New Bing。对比结果是New Bing比ChatGPT更强大。来看看具体对比例子吧 1.时效性更强 ChatGPT的库比较老&#xff0c;跟不上时事&#xff0c;比如你问它九转大肠的梗&#xff0c;ChatG…

初级调色转档CameraRaw

一级调色 还原-曝光-色彩-细节-质感 修图的范围 整体&#xff08;掌握基本面板&#xff09;——局部&#xff08;曲线&#xff09;——具象&#xff08;混色器&#xff09; 修片最开始的准备工作 看直方图:明暗跟色彩的数据表 分析图片是否存在以下问题&#xff1a; 1.曝光…

Linux权限概念

目录 Linux权限的概念 什么是权限 如何去操作权限 设置文件所属角色 设置文件属性 umask 粘滞位 Linux权限的概念 首先我们要了解到&#xff0c;在linux下有两种用户&#xff1a;超级用户(root)和普通用户。超级用户的命令提示符是“#”&#xff0c;普通用户的命令提示…

亚马逊测评自养号需要注意的五点

一.有自己的一套环境系统市场上有很多系统&#xff0c;但很多都是现成的或软件包&#xff0c;没有解决风控的能力&#xff0c;这个还需要大家自己甄别的哈&#xff0c;如果有需要建议大家自己学习一套&#xff0c;把技术掌握在自己手里&#xff0c;这样不会有依赖性&#xff0c…

零基础、学历无优势、逻辑能力一般”,能转行做程序员吗?

此前&#xff0c;拉勾数据研究院对程序员群体做了一次深入调查&#xff0c;并发布了《2022程序员群体职场洞察报告》&#xff0c;报告显示&#xff0c;“高薪”依然是程序员的职业标签之一。 在调查的程序员群体中&#xff0c;年薪在10-30万元之间的人数占比为66.7%&#xff0…

V4l2框架基础知识(三)

V4L2框架概述 V4L2框架主要部分组成&#xff1a; V4L2_device&#xff1a;管理所有设备&#xff1b; media_device:media_device框架管理运行时的pipeline&#xff1b; V4L2_device 这个是整个输入设备的总结构体&#xff0c;可以认为他是整个V4L2框架的入口&#xff0c;由该…

分享53戏源代码总有一个是你想要的(亲测每一个均可用)

收集整理不容易老铁支持我动力&#xff01; 53戏源代码下载链接&#xff1a;https://pan.baidu.com/s/1MI_EvHNFKoUu-l8LR6MAmg?pwdass5 提取码&#xff1a;ass5 下面是项目的名字&#xff0c;我放了一些图片&#xff0c;大家下载后可以看到。 c#版植物大战僵尸 H5纸牌接龙…

猿人学 第十六题

使用XHR断点找到XHR发送位置向上跟栈&#xff0c;会发现在ajax中发现m已经生成&#xff0c;向上追踪代码继续向上追&#xff0c;发现m在i中出现&#xff0c;并在r.m中生成进入n[e(528)]方法&#xff0c;发现作用只是返回e(t)p_s为获取当时时间的方法 btoa为方法点击进入btoa方法…

玩机搞机------高级改良型高通工程线制作方法。

很多普通玩家对工程线的定义还停留在早期短接红绿端的操作认知上。例如这个 常规制作工程线 或者短接主板触点方式 今天给大家描述的是更高级改良型的工程线。因为前期的这种工程线对于米6或者其他同时期机型已经不起任何作用。后期机型进入高通edl模式就需要拆机手动短接主板触…

pytorch各种版本最简单安装,不用自己安装cuda cudnn

pytorch各种版本 pip 安装命令 查看官网 https://pytorch.org/get-started/previous-versions/ conda pytorch 安装 1、安装conda&#xff0c; 2、创建并并激活虚拟环境 - conda create -n pytorch_1.7 python3.7 - conda activate pytorch_1.7 3、虚拟环境中 pip 安装想要的…

升级Room引发的惨案!!

kotlin升级 在升级kotlin的时候&#xff0c;直接升级到大版本的kotlin&#xff08;比如1.7以上&#xff09;&#xff0c;直接报错&#xff0c;只是报错不知道原因。 koltin Release details 后来把koltin版本改成1.6.0&#xff0c;报如下的错&#xff0c;我们才知道gradle是需…

积水内涝监测——埋入式积水终端设备介绍

一、设备概述 埋入式积水终端是针对城市内涝推出的积水信息监测采集设备&#xff0c;采用超声波传感技术&#xff0c;对积水的深度进行精确的测量。产品能够在低温、腐蚀环境下可靠运行本产品特别适用于智慧城市中&#xff0c;对城市道路、社区低洼处的积水进行实时监测上报到…

(十)、通过云对象修改阅读量+点赞功能的实现【uniapp+uinicloud多用户社区博客实战项目(完整开发文档-从零到完整项目)】

1&#xff0c;通过云对象importObj修改阅读量 1.1 新建云对象 1.2 云对象中写自增自减方法 封装云对象utilsObj中的自增自减方法&#xff0c;方法名取为operation&#xff0c;传递4个参数。 // 云对象教程: https://uniapp.dcloud.net.cn/uniCloud/cloud-obj // jsdoc语法提…

火山引擎数智平台的这款产品,正在帮助 APP 提升用户活跃度

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 你有没有关注过 APP 给你推送的消息&#xff1f; 出于提升用户活跃度的考虑&#xff0c;APP 会定期在应用内面向用户进行内通推送&#xff0c;推送形式既包括 APP …

查看jdk安装路径,在windows上实现多个java jdk的共存解决办法,安装java19后终端乱码的解决

查看jdk安装路径&#xff0c; 在windows上实现多个java jdk的共存解决办法&#xff0c; 安装java19后终端乱码的解决 目录 一、查看jdk&#xff08;java开发工具包&#xff09;安装路径的方法 二、在windows上实现多个java jdk的共存 &#xff08;1&#xff09;、安装好多…

【数据宝应用场景】智慧工地哪些场景需要用到哪些大数据?

近几年来&#xff0c;我国施工安全管理日趋严格&#xff0c;人力成本逐渐提升&#xff0c;人力成本压力逐渐增加&#xff0c;通过大数据、物联网、云计算和智能设备等软硬件信息化技术能有效提升工地的生产效率、管理效率等&#xff0c;实现工地的数字化、精细化、智慧化管理&a…

WGCNA | 值得你深入学习的生信分析方法!~(网状分析-第四步-模块的功能注释)

1写在前面 前面我们用WGCNA分析得到多个模块&#xff0c;其中有一些模块和我们感兴趣的表型或者临床特征是相关的。&#x1f973; 接着就是要做模块的富集分析了&#xff0c;帮助我们了解这些模块的基因都有哪些已知的功能&#xff0c;涉及到哪些通路&#xff0c;在哪些疾病中最…

贪心算法-蓝桥杯

一、贪心算法的优缺点优点&#xff1a;1.容易理解:生活常见。2.操作简单:在每一步都选局部最优。3.效率高: 复杂度常常是O(1)的。缺点&#xff1a;1.局部最优不一定是全局最优。二、例子: 最少硬币问题硬币面值1、2、5。支付13元&#xff0c;要求硬币数量最少。贪心法: (1) 5元…

工程项目管理系统源码之工程项目管理系统面临的挑战

​ ​工程项目管理系统是指从事工程项目管理的企业&#xff08;以下简称工程项目管理企业&#xff09;受业主委托&#xff0c;按照合同约定&#xff0c;代表业主对工程项目的组织实施进行全过程或若干阶段的管理和服务。 ​系统定义 工程项目管理企业不直接与该工程项目的总承包…