零基础设计模式——结构型模式 - 代理模式

article/2025/8/27 10:27:15

第三部分:结构型模式 - 代理模式 (Proxy Pattern)

在学习了享元模式如何通过共享对象来优化资源使用后,我们来探讨结构型模式的最后一个模式——代理模式。代理模式为另一个对象提供一个替身或占位符以控制对这个对象的访问。

  • 核心思想:为其他对象提供一种代理以控制对这个对象的访问。

代理模式 (Proxy Pattern)

“为其他对象提供一种代理以控制对这个对象的访问。” (Provide a surrogate or placeholder for another object to control access to it.)

想象一下,你想看一张非常大的高清图片,但加载它需要很长时间。或者,你可能需要访问一个远程服务器上的资源,网络延迟很高。或者,你可能需要对某个操作进行权限检查,只有特定用户才能执行。

代理模式通过引入一个代理对象来间接访问真实对象(也称为主题对象或服务对象)。客户端与代理对象交互,代理对象再根据需要与真实对象交互。

  • 真实主题 (Real Subject):实际执行任务的对象,如大图片加载器、远程服务接口、需要权限的操作。
  • 代理 (Proxy):控制对真实主题访问的替身。它可以实现与真实主题相同的接口,使得客户端可以无缝切换。

1. 目的 (Intent)

代理模式的主要目的:

  1. 控制访问:代理可以控制客户端对真实对象的访问权限、时机或方式。
  2. 提供间接层:在客户端和真实对象之间引入一个间接层,可以在这个层面上执行额外的操作,如延迟加载、缓存、日志记录、权限验证等。
  3. 简化复杂性:代理可以隐藏真实对象的复杂性,例如远程调用的网络细节。

2. 生活中的例子 (Real-world Analogy)

  • 信用卡

    • 你的银行账户(真实主题)里有钱。
    • 信用卡(代理)是你访问银行账户资金的一种方式。当你刷卡时,信用卡公司会验证你的身份、检查账户余额(控制访问),然后才允许交易。
  • 经纪人/中介

    • 你想买卖股票(真实主题是股票交易所)。
    • 你通过股票经纪人(代理)进行操作。经纪人会处理交易的细节,你不需要直接与交易所打交道。
    • 房产中介(代理)帮助你买卖房屋(真实主题是房产本身和房主)。
  • 门禁系统

    • 大楼的某个区域(真实主题)是受限的。
    • 门禁卡或保安(代理)验证你的身份和权限,决定是否允许你进入。
  • 明星的经纪人

    • 明星(真实主题)很忙。
    • 经纪人(代理)处理明星的日程安排、商业洽谈等,过滤掉不必要的干扰,并代表明星处理事务。

3. 结构 (Structure)

代理模式通常包含以下角色:

  1. Subject (主题接口):定义了 RealSubject 和 Proxy 的共同接口。这样,在任何使用 RealSubject 的地方都可以使用 Proxy。
  2. RealSubject (真实主题):定义了 Proxy 所代表的真实实体。这是实际执行业务逻辑的对象。
  3. Proxy (代理类)
    • 保存一个引用使得代理可以访问实体。若 RealSubject 和 Subject 的接口相同,Proxy 会引用 Subject。
    • 提供一个与 Subject 的接口相同的接口,这样代理就可以用来替代实体。
    • 控制对实体的存取,并可能负责创建和删除它。
    • 其他功能依赖于代理的类型。
  4. Client (客户端):通过 Subject 接口与 RealSubject 或 Proxy 交互。
    在这里插入图片描述
    工作流程
  • 客户端请求操作时,它会调用 Proxy 对象的方法。
  • Proxy 对象可能会执行一些预处理操作(如权限检查、日志记录)。
  • 如果需要,Proxy 会创建或获取 RealSubject 对象的引用,并将请求委托给 RealSubject
  • RealSubject 执行实际的操作。
  • Proxy 可能会执行一些后处理操作(如结果缓存、日志记录),然后将结果返回给客户端。

4. 常见代理类型 (Types of Proxies)

根据代理的目的和实现方式,有几种常见的代理类型:

  1. 虚拟代理 (Virtual Proxy)

    • 目的:延迟加载昂贵的对象。当创建真实对象的开销很大时,虚拟代理会推迟真实对象的创建,直到客户端真正需要它为止。
    • 例子:显示一个包含大量图片的文档,图片对象(真实主题)可以在实际滚动到屏幕上时才由虚拟代理创建和加载。
  2. 远程代理 (Remote Proxy)

    • 目的:为位于不同地址空间(如另一台机器上)的对象提供本地代表。远程代理负责处理网络通信的细节(如序列化、连接管理),使得客户端感觉像在调用本地对象一样。
    • 例子:Java RMI (Remote Method Invocation) 中的 Stub 对象就是远程代理。
  3. 保护代理 (Protection Proxy)

    • 目的:控制对真实对象的访问权限。在调用真实对象的方法之前,保护代理会检查客户端是否具有相应的权限。
    • 例子:根据用户角色控制对某些敏感操作的访问。
  4. 智能引用代理 (Smart Reference / Smart Proxy)

    • 目的:在访问对象时执行一些额外的操作,如引用计数、加锁以控制并发访问、对象加载时记录日志等。
    • 例子:C++ 中的智能指针(如 std::shared_ptr)可以看作是一种智能引用代理,负责管理对象的生命周期。
  5. 缓存代理 (Caching Proxy)

    • 目的:为开销大的操作结果提供临时存储。当多个客户端请求相同的结果时,可以直接从缓存中返回,避免重复计算或请求。
    • 例子:Web 代理服务器缓存常用网页;应用程序缓存数据库查询结果。
  6. 日志代理 (Logging Proxy)

    • 目的:在方法调用前后记录日志信息。

5. 适用场景 (When to Use)

  • 当你需要延迟初始化一个开销很大的对象时(虚拟代理)。
  • 当你需要控制对一个对象的访问权限时(保护代理)。
  • 当你需要为一个远程对象提供本地代表时(远程代理)。
  • 当你需要在访问对象时执行一些附加操作,如日志记录、缓存、事务管理等(智能引用代理、缓存代理、日志代理)。
  • 当你希望为一个对象提供不同级别的访问权限时。

6. 优缺点 (Pros and Cons)

优点:

  1. 控制访问:代理模式的核心优势在于能够控制对真实对象的访问。
  2. 增强功能:可以在不修改真实对象代码的情况下,通过代理为其增加额外的功能(如延迟加载、权限控制、日志、缓存)。
  3. 降低耦合:客户端与真实对象解耦,客户端只与代理接口交互。
  4. 提高性能:通过虚拟代理延迟加载或缓存代理缓存结果,可以提高系统性能。
  5. 远程访问透明化:远程代理使得客户端可以像访问本地对象一样访问远程对象。

缺点:

  1. 增加系统复杂性:引入了额外的代理类,可能会增加系统的设计和实现的复杂度。
  2. 可能引入性能开销:由于请求需要通过代理转发,可能会增加一次间接调用,带来轻微的性能延迟。但通常这种开销被代理带来的好处(如延迟加载、缓存)所抵消或超过。
  3. 真实主题的接口依赖:代理类通常依赖于真实主题的接口,如果真实主题接口发生变化,代理类也可能需要修改。

7. 实现方式 (Implementations)

让我们以一个虚拟代理为例,实现一个图片加载器。真实图片对象加载开销大,我们希望在实际显示时才加载它。

主题接口 (Image - Subject)
// image.go (Subject interface)
package imaging// Image 主题接口
type Image interface {Display()GetFilename() string
}
// Image.java (Subject interface)
package com.example.imaging;// 主题接口
public interface Image {void display();String getFilename();
}
真实主题 (RealImage - RealSubject)
// real_image.go (RealSubject)
package imagingimport ("fmt""time"
)// RealImage 真实主题
type RealImage struct {filename string
}func NewRealImage(filename string) *RealImage {ri := &RealImage{filename: filename}ri.loadFromDisk() // 创建时即加载return ri
}func (ri *RealImage) GetFilename() string {return ri.filename
}func (ri *RealImage) loadFromDisk() {fmt.Printf("RealImage: Loading image '%s' from disk...\n", ri.filename)// 模拟耗时操作time.Sleep(2 * time.Second)fmt.Printf("RealImage: Image '%s' loaded.\n", ri.filename)
}func (ri *RealImage) Display() {fmt.Printf("RealImage: Displaying image '%s'\n", ri.filename)
}
// RealImage.java (RealSubject)
package com.example.imaging;// 真实主题
public class RealImage implements Image {private String filename;public RealImage(String filename) {this.filename = filename;loadFromDisk(); // 创建时即加载}@Overridepublic String getFilename() {return filename;}private void loadFromDisk() {System.out.printf("RealImage: Loading image '%s' from disk...%n", filename);try {// 模拟耗时操作Thread.sleep(2000);} catch (InterruptedException e) {Thread.currentThread().interrupt();System.err.println("Loading interrupted for " + filename);}System.out.printf("RealImage: Image '%s' loaded.%n", filename);}@Overridepublic void display() {System.out.printf("RealImage: Displaying image '%s'%n", filename);}
}
代理类 (ProxyImage - Proxy)
// proxy_image.go (Proxy)
package imagingimport "fmt"// ProxyImage 代理类 (虚拟代理)
type ProxyImage struct {filename  stringrealImage *RealImage // 指向真实对象的指针,延迟初始化
}func NewProxyImage(filename string) *ProxyImage {fmt.Printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.\n", filename)return &ProxyImage{filename: filename, realImage: nil}
}func (pi *ProxyImage) GetFilename() string {return pi.filename
}func (pi *ProxyImage) Display() {if pi.realImage == nil { // 延迟加载fmt.Printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...\n", pi.filename)pi.realImage = NewRealImage(pi.filename)}pi.realImage.Display() // 委托给真实对象
}
// ProxyImage.java (Proxy)
package com.example.imaging;// 代理类 (虚拟代理)
public class ProxyImage implements Image {private String filename;private RealImage realImage; // 指向真实对象的引用,延迟初始化public ProxyImage(String filename) {this.filename = filename;System.out.printf("ProxyImage: Created proxy for image '%s'. Real image not loaded yet.%n", filename);}@Overridepublic String getFilename() {return filename;}@Overridepublic void display() {if (realImage == null) { // 延迟加载System.out.printf("ProxyImage: Real image '%s' needs to be displayed. Loading now...%n", filename);realImage = new RealImage(filename);}realImage.display(); // 委托给真实对象}
}
客户端使用
// main.go (示例用法)
/*
package mainimport ("./imaging""fmt"
)func main() {fmt.Println("--- Client: Creating proxy images ---")image1 := imaging.NewProxyImage("photo1.jpg")image2 := imaging.NewProxyImage("document_scan.png")// 此时,真实图片尚未加载fmt.Printf("\nImage 1 Filename: %s\n", image1.GetFilename())fmt.Printf("Image 2 Filename: %s\n", image2.GetFilename())fmt.Println("\n--- Client: Requesting to display image1 ---")image1.Display() // 第一次调用 Display,会触发真实图片加载fmt.Println("\n--- Client: Requesting to display image1 again ---")image1.Display() // 第二次调用 Display,真实图片已加载,直接显示fmt.Println("\n--- Client: Requesting to display image2 ---")image2.Display() // 第一次调用 Display for image2,会触发加载
}
*/
// Main.java (示例用法)
/*
package com.example;import com.example.imaging.Image;
import com.example.imaging.ProxyImage;public class Main {public static void main(String[] args) {System.out.println("--- Client: Creating proxy images ---");Image image1 = new ProxyImage("photo1.jpg");Image image2 = new ProxyImage("document_scan.png");// 此时,真实图片尚未加载System.out.printf("%nImage 1 Filename: %s%n", image1.getFilename());System.out.printf("Image 2 Filename: %s%n", image2.getFilename());System.out.println("%n--- Client: Requesting to display image1 ---");image1.display(); // 第一次调用 display,会触发真实图片加载System.out.println("%n--- Client: Requesting to display image1 again ---");image1.display(); // 第二次调用 display,真实图片已加载,直接显示System.out.println("%n--- Client: Requesting to display image2 ---");image2.display(); // 第一次调用 display for image2,会触发加载}
}
*/

8. 与装饰器模式的区别

代理模式和装饰器模式在结构上非常相似(都包装了另一个对象并实现了相同的接口),但它们的意图截然不同:

  • 代理模式 (Proxy)

    • 意图:控制对对象的访问。代理决定客户端是否、何时以及如何访问真实对象。
    • 关注点:访问控制、生命周期管理(如虚拟代理)、通信(如远程代理)。
    • 客户端感知:客户端可能不知道它正在与代理交互(例如,远程代理或保护代理对客户端是透明的),也可能知道(例如,客户端显式创建一个虚拟代理)。
    • 谁创建:代理通常由系统或框架创建和管理,或者客户端在特定场景下创建(如虚拟代理)。
  • 装饰器模式 (Decorator)

    • 意图:动态地向对象添加额外的职责或行为,而不改变其接口。
    • 关注点:增强对象的功能。
    • 客户端感知:客户端通常知道它正在使用一个装饰过的对象,并且通常负责构建装饰链。
    • 谁创建:装饰器通常由客户端根据需要动态地组合和应用。

简单来说:

  • 代理:我是“替身”或“看门的”,我管着你怎么用那个真实的东西。
  • 装饰器:我是“加料的”,我给那个真实的东西增加新花样。

9. 总结

代理模式是一种强大的结构型模式,它通过引入一个代理对象来控制对真实对象的访问。这种间接性使得我们可以在不修改真实对象代码的情况下,实现诸如延迟加载、权限控制、远程访问、日志记录、缓存等多种功能。根据具体需求,可以选择不同类型的代理(虚拟代理、保护代理、远程代理等)来解决特定的问题。

记住它的核心:提供替身,控制访问,增强间接性


http://www.hkcw.cn/article/EGEqpVXdnp.shtml

相关文章

从 0 到 1 的显示革命:九天画芯张锦解码铁电液晶技术进化史

一、显示技术困局:传统液晶的天花板在哪里? 在消费电子与工业显示高速发展的今天,传统液晶技术正遭遇物理极限挑战。受 “边缘场效应” 制约,液晶分子因粘附像素格电极边框,仅中心区域可自由旋转,边缘分子的…

MySql(六)

插入数据 对mysql的表中的数据进行插入数据操作 语法格式: insert into 表名 (字段名1,字段名2..) values (字段值1,字段值2...) 这个有点类似键值对的关系。 一对一 1)首先准备一张表 /* Navicat Pre…

leetcode:372. 超级次方(python3解法,数学相关算法题)

难度:中等 你的任务是计算 ab 对 1337 取模,a 是一个正整数,b 是一个非常大的正整数且会以数组形式给出。 示例 1: 输入:a 2, b [3] 输出:8示例 2: 输入:a 2, b [1,0] 输出&…

C++ —(详述c++特性)

一 namespeace(命名空间) namespace是一个自定义的空间,这个空间相当于一个总文件夹,总文件可以有好多个,里面的小文件夹或者其他文件,也可以有其他各种各样的文件, 定义:命名空间是…

20250529-C#知识:属性

C#知识:属性 在开发过程中,在需要public读取并且不允许从外界修改的情况下经常会用到属性。本文简单介绍一下属性。 1、主要内容及代码示例 属性类似成员变量属性包括get和set语句块属性能单独为get和set设置访问权限属性能为get和set操作添加处理逻辑g…

知识课堂|sCMOS相机可编程快门模式解析

sCMOS相机凭借高灵敏度、高动态、低读出噪声特性,成为生命科学成像领域的核心设备。在光片荧光显微镜LSFM成像应用中,传统卷帘快门的时序限制可能引发运动伪影或光片照明不均匀问题。可编程快门模式通过精确控制传感器曝光时序,实现与激光扫描…

Apache Kafka 实现原理深度解析:生产、存储与消费全流程

Apache Kafka 实现原理深度解析:生产、存储与消费全流程 引言 Apache Kafka 作为分布式流处理平台的核心,其高吞吐、低延迟、持久化存储的设计使其成为现代数据管道的事实标准。本文将从消息生产、持久化存储、消息消费三个阶段拆解 Kafka 的核心实现原…

已解决:.NetCore控制台程序(WebAPI)假死,程序挂起接口不通

本问题已得到解决,请看以下小结: 关于《.NetCore控制台程序(WebAPI)假死,程序暂停接口不通》的解决方案 记录备注报错时间2025年报错版本VS2022 WINDOWS10报错复现鼠标点一下控制台,会卡死报错描述——报错截图——报错原因 控制台启用了“快…

π0-通用VLA模型-2024.11.13-开源

π 0 π0 π0在2025.2.4开源,目前在github有3.4k的星标,说他是通用策略表现在两点上: 做的任务是多元的而且都比较复杂,比如叠衣服,从洗衣机里拿出衣服等等既可以控制单臂,又可以双臂,还可以控…

基于本地化大模型的智能编程助手全栈实践:从模型部署到IDE深度集成学习心得

近年来,随着ChatGPT、Copilot等AI编程工具的爆发式增长,开发者生产力获得了前所未有的提升。然而,云服务的延迟、隐私顾虑及API调用成本促使我探索一种更自主可控的方案:基于开源大模型构建本地化智能编程助手。本文将分享我构建本…

机器视觉2,硬件选型

机器视觉1,学习了硬件的基本知识和选型,现在另外的教材巩固知识 选相机 工业相机选型的保姆级教程_哔哩哔哩_bilibili 1.先看精度多少mm,被检测物体长宽多少mm》分辨率, 选出合理范围内的相机 2.靶面尺寸,得出分…

预处理,咕咕咕

1.预定义符号 _FILE_ //编译的源文件 _LINE_ //文件行号 _DATA_ //文件编译日期 _TIME_ //文件编译时间 _STDC_ //如果文件编译遵循ANSI C,其值为一,否则未定义 printf("%d",_FILE_,_LINE_);2.#define定义常量 #define name stuff #define MAX 100…

UI自动化测试的革新,新一代AI工具MidScene.js实测!

前言 AI已经越来越深入地走入我们的实际工作,在软件测试领域,和AI相关的新测试工具、方法也层出不穷。在之前我们介绍过结合 mcp server 实现 AI 驱动测试的案例,本文我们将介绍一个近期崭露头角的国产AI测试工具 Midscene.js Midscene.js简介 MidScene.js 是由字节跳动 w…

什么是CVSD

CVSD(Continuous Variable Slope Delta Modulation,连续可变斜率增量调制) 是一种用于蓝牙语音通话的超低复杂度音频编码技术。它通过1比特量化实时跟踪音频信号的变化趋势,是早期蓝牙设备(HSP/HFP 1.0-1.5&#xff09…

MyBatis动态SQL

还不了解MySQL的可以看我这篇文章: MyBatis入门:快速搭建数据库操作框架 两种增删改查的方法(CRUD)-CSDN博客 动态 SQL 是Mybatis的强⼤特性之⼀,能够完成不同条件下不同的 sql 拼接 官方文档:动态 SQL_MyBatis中文网 创建相关…

分享智能技能提升6月份排考计划

中级: 6月13号考试 AIGC应用工程师(初级) 高级: 6月15号考试 人工智能应用工程师(高级) 大数据分析师(高级) AIGC应用工程师(高级) 大数据/数字技术应用工程师…

防火墙ASPF(针对应用层包过滤技术) FTP(主动模式)

1.实验拓扑 2.基础配置 IP地址配置省略 [FW1-policy-security]di th 2025-05-29 12:20:13.740 # security-policy rule name trust->dmz source-zone trust destination-zone dmz source-address 10.1.11.0 mask 255.255.255.0 destination-address 10.1.21.0 ma…

RDS PostgreSQL手动删除副本集群副本的步骤

由于PostgreSQL不支持直接删除副本集群,而是需要先将副本集群升级到主实例(区域集群),然后在逐一将写入器实例删除,然后才可以删除副本集群 查看现有的主从实例集群 将副本集群提升到区域集群 选择副本集群–>操作–>提升 提升只读副本…

矿用电控系统专用配件铜头铠装4C型护套连接器

矿用电控系统专用配件铜头铠装4C型护套连接器是矿山电气设备中不可或缺的关键组件,其设计、性能与可靠性直接关系到井下作业的安全性和生产效率。随着智能化矿山建设的推进,对连接器的技术要求日益提高,铜头铠装4C型护套连接器凭借其独特的结…

【二】9.关于pinctrl和gpio子系统

前言: 为什么要有pinctrl和gpio子系统呢?--->>>因为LZ不在想推着凯迪拉克其上班了。 1.pinctrl子系统: 因为 ST 针对 STM32MP1 提供的 Linux 系统中,其 pinctrl 配置的电气属性只能在platform 平台下被引用,…