设计模式(十三)结构型:代理模式详解

代理模式(Proxy Pattern)是 GoF 23 种设计模式中的结构型模式之一,其核心价值在于为其他对象提供一种间接访问的机制,以控制对原始对象的访问。它通过引入一个“代理”对象,作为客户端与真实对象之间的中介,从而在不改变原始接口的前提下,实现访问控制、延迟初始化、日志记录、权限校验、缓存、远程通信等附加功能。代理模式是实现“开闭原则”和“单一职责原则”的重要手段,广泛应用于远程服务调用(RMI、Web Service)、虚拟代理(延迟加载)、保护代理(权限控制)、智能引用(资源管理)等场景,是构建安全、高效、可维护系统的关键架构模式。

一、详细介绍

代理模式解决的是“直接访问目标对象存在限制或需要增强控制”的问题。在某些情况下,客户端不能或不应直接访问真实对象,例如:

  • 对象创建代价高昂(如大型图像、数据库连接),需延迟加载;
  • 对象位于远程主机,需通过网络访问;
  • 对象涉及敏感操作,需进行权限验证;
  • 需要监控对象的访问行为(如调用次数、执行时间)。

代理模式通过一个与真实对象具有相同接口的代理对象,拦截所有对真实对象的请求,并在转发前或后执行额外逻辑。客户端通过代理与真实对象交互,整个过程对客户端透明。

该模式包含以下核心角色:

  • Subject(抽象主题):定义真实对象和代理对象的公共接口,客户端通过该接口访问目标。可以是接口或抽象类。
  • RealSubject(真实主题):实现 Subject 接口,是代理所代表的真实对象,包含核心业务逻辑。
  • Proxy(代理类):实现 Subject 接口,持有对 RealSubject 的引用。它控制对真实对象的访问,可在调用前后执行额外操作(如检查权限、缓存结果、记录日志)。

根据使用目的不同,代理模式可分为多种类型:

  1. 远程代理(Remote Proxy):为位于不同地址空间的对象提供本地代表,隐藏网络通信细节(如 RMI、gRPC Stub)。
  2. 虚拟代理(Virtual Proxy):延迟创建开销大的对象,直到真正需要时才初始化(如图片懒加载)。
  3. 保护代理(Protection Proxy):控制对对象的访问权限,根据角色决定是否允许操作(如管理员 vs 普通用户)。
  4. 智能引用(Smart Reference):在访问对象时执行额外操作,如引用计数、空指针检查、缓存结果。
  5. 缓存代理(Caching Proxy):缓存真实对象的操作结果,提高性能,避免重复计算或远程调用。

代理模式的关键优势:

  • 增强控制:可在访问前后插入逻辑,实现横切关注点。
  • 解耦客户端与真实对象:客户端不依赖具体实现,便于替换或扩展。
  • 提高安全性:通过保护代理实现权限隔离。
  • 优化性能:通过虚拟代理延迟加载,缓存代理减少重复操作。

与“装饰器模式”相比,代理关注访问控制,装饰器关注功能增强;代理通常不改变对象行为本质,而是控制何时、如何访问;装饰器则明确添加新功能。与“外观模式”相比,代理封装的是单个对象的访问,外观封装的是多个子系统的协作

二、代理模式的UML表示

以下是代理模式的标准 UML 类图:

implements
implements
has a
uses
«interface»
Subject
+request()
RealSubject
+request()
Proxy
-realSubject: RealSubject
+request()
+preRequest()
+postRequest()
Client
-subject: Subject
+doWork()

图解说明

  • Subject 是统一接口,客户端通过它与真实对象或代理交互。
  • RealSubject 是真实业务对象。
  • Proxy 持有 RealSubject 引用,在 request() 中可调用 preRequest()postRequest() 执行额外逻辑。
  • 客户端通过 Subject 接口调用,无法区分是代理还是真实对象。

三、一个简单的Java程序实例及其UML图

以下是一个文档管理系统中“受保护的文件访问”示例,展示如何使用保护代理控制对敏感文件的访问。

Java 程序实例
// 抽象主题:文件访问接口
interface Document {void open();void edit();
}// 真实主题:实际文档对象
class RealDocument implements Document {private String fileName;public RealDocument(String fileName) {this.fileName = fileName;loadFromDisk(); // 模拟耗时操作}private void loadFromDisk() {System.out.println("📄 正在从磁盘加载文档: " + fileName);try {Thread.sleep(1000); // 模拟加载延迟} catch (InterruptedException e) {Thread.currentThread().interrupt();}System.out.println("✅ 文档 " + fileName + " 加载完成");}@Overridepublic void open() {System.out.println("🔓 打开文档: " + fileName);}@Overridepublic void edit() {System.out.println("✍️ 编辑文档: " + fileName);}
}// 代理类:保护代理,控制文档访问权限
class ProtectedDocumentProxy implements Document {private String fileName;private RealDocument realDocument; // 延迟初始化private String currentUser;private boolean isAdmin;public ProtectedDocumentProxy(String fileName, String currentUser, boolean isAdmin) {this.fileName = fileName;this.currentUser = currentUser;this.isAdmin = isAdmin;}@Overridepublic void open() {if (canAccess()) {// 虚拟代理:延迟加载真实对象if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("open");realDocument.open();} else {System.out.println("❌ 用户 " + currentUser + " 无权打开文档: " + fileName);}}@Overridepublic void edit() {if (canModify()) {if (realDocument == null) {realDocument = new RealDocument(fileName);}logAccess("edit");realDocument.edit();} else {System.out.println("❌ 用户 " + currentUser + " 无权编辑文档: " + fileName);}}// 权限检查:读取权限private boolean canAccess() {return isAdmin || currentUser.equals("owner");}// 权限检查:修改权限private boolean canModify() {return isAdmin; // 只有管理员可编辑}// 访问日志private void logAccess(String operation) {System.out.println("📝 日志: 用户 [" + currentUser + "] 执行 [" + operation + "] 操作 on " + fileName);}
}// 客户端使用示例
public class ProxyPatternDemo {public static void main(String[] args) {System.out.println("🔐 文档管理系统 - 保护代理示例\n");// 创建代理对象(不立即加载真实文档)Document doc = new ProtectedDocumentProxy("财务报告.docx", "alice", false);// 普通用户尝试打开文档System.out.println("👉 用户 alice (普通用户) 尝试打开文档:");doc.open(); // 允许打开System.out.println("\n👉 用户 alice 尝试编辑文档:");doc.edit(); // 拒绝编辑System.out.println("\n" + "=".repeat(50) + "\n");// 管理员访问Document adminDoc = new ProtectedDocumentProxy("财务报告.docx", "admin", true);System.out.println("👉 用户 admin (管理员) 尝试打开并编辑文档:");adminDoc.open();adminDoc.edit();System.out.println("\n💡 说明:真实文档仅在首次访问时加载,且权限由代理控制。");}
}
实例对应的UML图(简化版)
implements
implements
creates on demand
uses
«interface»
Document
+open()
+edit()
RealDocument
-fileName: String
+RealDocument(fileName: String)
+open()
+edit()
ProtectedDocumentProxy
-fileName: String
-realDocument: RealDocument
-currentUser: String
-isAdmin: boolean
+open()
+edit()
+canAccess()
+canModify()
+logAccess(operation: String)
Client
+main(args: String[])

运行说明

  • ProtectedDocumentProxy 实现了 Document 接口,持有文件名和用户信息。
  • 真实文档 RealDocument 在首次 open()edit() 时才创建(虚拟代理特性)。
  • 代理在调用前检查权限(保护代理),并记录访问日志(智能引用)。
  • 客户端通过统一接口操作,无法感知代理的存在。

四、总结

特性说明
核心目的控制对对象的访问,增强安全性与灵活性
实现机制实现相同接口,持有真实对象引用,拦截并转发请求
优点访问控制、延迟加载、日志监控、远程透明化、解耦客户端
缺点增加系统复杂性、可能引入性能开销(间接调用)、需维护代理逻辑
适用场景权限控制、远程服务、延迟初始化、缓存、资源管理、AOP
不适用场景对象简单、无需控制、性能极度敏感

代理模式使用建议

  • 代理类应尽量轻量,避免成为性能瓶颈。
  • 可结合工厂模式或依赖注入创建代理。
  • 在 Java 中,动态代理(java.lang.reflect.Proxy)可减少静态代理的类膨胀问题。
  • Spring AOP 的 JDK Dynamic ProxyCGLIB 是代理模式的高级应用。

架构师洞见:
代理模式是“间接性”与“控制力”的完美结合。在现代架构中,其思想已渗透到微服务治理API 网关服务网格(Istio/Linkerd)AOP(面向切面编程) 的核心。例如,在服务网格中,Sidecar 代理拦截所有进出服务的流量,实现熔断、限流、加密;在 Spring 框架中,@Transactional 注解通过代理实现声明式事务管理;在前端,Proxy 对象用于实现响应式数据监听(如 Vue 3)。

未来趋势是:代理模式将与AI 安全网关结合,智能代理可动态分析请求内容并决定是否放行;在边缘计算中,设备代理将统一管理异构设备的通信协议;在元编程运行时增强中,代理将成为实现热更新、动态配置的核心机制。

掌握代理模式,有助于设计出安全、可控、可监控的系统。作为架构师,应在系统边界、服务入口、资源访问点主动引入代理层,将“控制逻辑”与“业务逻辑”分离。代理不仅是模式,更是系统治理的哲学——它告诉我们:真正的掌控力,来自于对“访问路径”的精心设计与智能干预。

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

24点数学游戏(穷举法求解表达式)

摘要本毕业设计旨在利用MATLAB技术实现一个24点数学游戏,采用穷举法求解所有可能的表达式组合。通过全排列数字、枚举运算符及括号位置,结合递归回溯算法,系统能够高效地搜索所有可能的运算路径,并验证结果是否为24。实验结果表明…

【web应用】如何进行前后端调试Debug? + 前端JavaScript调试Debug?

文章目录一、前后端:后端以Debug模式运行后端项目,打断点二、前后端:前端项目在浏览器中调试三、单独前端:前端JavaScript调试1、控制台输出2、网页调试器中添加断点3、debugger关键字一、前后端:后端以Debug模式运行后…

FreeCAD开发楼梯参数化三维模型和钢格栅

根据楼梯标准图集开发各种楼梯。上行左转,上行右转,对应的栏杆也是配套2种。楼梯总成钢格栅标准里的跨度和承载 扁钢尺寸,轻松切换和修改参数。格栅综合本来格栅上横杆是冷轧扭钢筋,先绘制一个圆柱,再做一个内切正方形…

【AcWing 836题解】合并集合

AcWing 836. 合并集合 【题目描述】 在查看解析之前,先给自己一点时间思考哦! 【题解】 并查集是一种用于处理集合合并与查询问题的数据结构,通常支持以下两种操作: Find:查询一个元素所在的集合。 Union&#xff1a…

MySQL锁机制与MVCC原理剖析

在MySQL中,我们使用到了它的各种类锁;按照它的维度,有各种锁 从数据库的操作粒度有,表锁,行锁。从数据库的操作的类型,有读锁和写锁。性能上有乐观锁和悲观锁。 在上一篇文章中的事务隔离级别,需…

C++学习(线程相关)

目录 一、线程库thread 1.使用外部函数 2. 使用类的函数 3. 添加参数 二、线程库 mutex 1.使用lock()方法 2.try_lock()方法 三、线程库lock_guard 四、线程库unique_lock 1.adopt_lock 2.defer_lock() 五、线程库call_once 六、线程库promise & future 七、c…

EPOLLONESHOT 深度解析:Linux epoll 的单次触发机制

EPOLLONESHOT 深度解析:Linux epoll 的单次触发机制 EPOLLONESHOT 是 Linux epoll 接口中的高级事件标志,用于实现精确的事件单次触发控制。以下是其全面技术解析: 核心设计理念 #mermaid-svg-Xg5sCLdddqmKsvKG {font-family:"trebuchet…

深入解析MongoDB分片原理与运维实践指南

深入解析MongoDB分片原理与运维实践指南 技术背景与应用场景 随着互联网业务的高速发展,单节点MongoDB实例在数据量和访问并发上都面临瓶颈。为了解决数据存储容量受限和读写性能下降的问题,MongoDB官方提供了分片(Sharding)方案&…

基于Django的天气数据可视化分析预测系统

【86-Django】基于Django的天气数据可视化分析预测系统(完整系统源码开发笔记详细部署教程)✅ 目录 一、项目简介 二、项目界面展示 三、项目视频展示 四、技术架构 五、核心功能模块 六、部署教程一、项目简介 随着全球气候变化和极端天气事件的频发&am…

怎么放大单片机输出电流

单片机作为电子系统的控制核心,其 I/O 口输出电流通常较小(一般在 10-20mA 左右),难以直接驱动继电器、电机、大功率 LED 等需要较大工作电流的外设。因此,在实际应用中需通过特定电路放大单片机输出电流,实…

站长百科类网站pbootcms模板(自适应手机端)+利于SEO优化(下载)

站长百科类网站pbootcms模板(自适应手机端)利于SEO优化 模板介绍: PbootCMS内核开发的模板,该模板属于新闻资讯、新闻博客类企业使用! 页面简洁简单,容易管理,附带测试数据! 模板特点: 1、手工书…

【Golang】Go语言函数

Go语言函数 文章目录Go语言函数Go函数特点一、函数的基本格式定义二、匿名函数三、自执行函数四、闭包函数五、延迟调用Go函数特点 无需声明原型支持不定 变参支持多返回值支持匿名函数和闭包函数也是一种类型,一个函数可以赋值给变量不支持嵌套,一个包…

JAVA算法练习题day2

双指针4.移动零二刷昨天的题,学习了新的数据结构StringBuilder。专为频繁字符串拼接设计的可变字符串类。(https://blog.csdn.net/m0_73941339/article/details/145651287)二刷完昨天的题目,做到这题脑子已经转不动了。做双指针,一般双指针初…

LLM2Rec-新国立-KDD2025-微调LLM获得蕴含协同信息的embedding

文章目录1. 背景与问题任务背景动机LLM2Rec 两大步骤2. 方法2.1 Collaborative Supervised Fine-tuning(CSFT)2.2 Item-level Embedding Modeling2.2.1 从单向注意力 → 双向注意力(Bidirectional attention)2.2.2 商品级别的对比…

前端学习9:JavaScript--对象与原型

前言:适合有基础的同学入门尝试 / 复习回忆。对象基础:1.创建用户对象const user {// 属性(键值对)name: "小岛",age: 20,isAdmin: false, }2.方法(函数属性)sayHello() {console.log(你好&…

网络:应用层

网络:应用层 我们要知道,所有的问题解决都是在应用层。:happy: 协议是一种约定,也就是双方约定好的结构化的数据。但是在读写数据时我们都是按字符串的方式来发送接受的,那么我们应该如和传输结构化的数据呢?应用层协…

rust-包和箱子

📦 图解 Rust 代码组织层级 #mermaid-svg-fBDy1PDZZ6bi000z {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-fBDy1PDZZ6bi000z .error-icon{fill:#552222;}#mermaid-svg-fBDy1PDZZ6bi000z .error-text{fi…

C++算法竞赛篇(五)循环嵌套题型讲解

C算法竞赛篇(五)循环嵌套题型讲解前言C循环嵌套题型讲解第一题 包含数字的9第二题 求出 e 的值第三题 斐波那契数列第四题 第 n 小的质数第五题 水仙花数前言 前面的题型里我们认识了C里面的三大循环本篇博客我们开始讲解C循环嵌套题型 我的个人主页&am…

Gradio全解8——ChatInterfaceChatbot:聊天界面类与聊天机器人(3)——ChatInterface的多模态功能与附加输入输出

Gradio全解8——ChatInterface&Chatbot:聊天界面类与聊天机器人(3)——ChatInterface的多模态功能与附加输入输出8.3 ChatInterface的多模态功能与附加输入输出8.3.1 多模态功能1. 设置multimodal和fn参数2. 传入MultimodalTextbox组件及…

php算法-- 关联数组使用,优化sip账号去重

文章目录1 变量定义2. 核心特性code1 变量定义 类型:嵌套的关联数组(Nested Associative Array)外层结构:[中继ID > 账号列表]键 (Key):中继ID(字符串或整型)值 (Value):索引数组…