GoF设计模式——代理模式

发布时间:2026/6/23 11:53:35
GoF设计模式——代理模式 为什么需要代理模式有时候我们不能或不想直接访问某个对象。比如对象创建开销很大需要延迟加载或者需要在访问前做权限检查或者需要记录访问日志。直接在业务代码中掺杂这些逻辑会让代码臃肿且难以维护。代理模式通过引入一个中间层将这些控制逻辑从业务代码中分离出来。客户端代码不需要知道它是在和代理交互还是真实对象交互两者可以透明替换。概念代理模式Proxy Pattern是一种结构型设计模式核心思想是为一个对象提供一个替身代理以控制对这个对象的访问。代理对象和真实对象实现相同的接口客户端通过代理间接访问真实对象代理可以在调用前后添加额外的控制逻辑。代理模式的主要角色有Subject抽象主题声明真实主题和代理共同实现的业务方法客户端面向该接口编程RealSubject真实主题定义代理所代表的真实对象是客户端最终要访问的对象Proxy代理持有对真实主题的引用实现与真实主题相同的接口在调用真实主题前后添加控制逻辑类图展示了静态结构但对代理模式这种调用拦截的场景时序图更能体现动态调用流程RealSubjectProxyClientRealSubjectProxyClientrequest()前置处理权限/日志/延迟加载request()返回结果后置处理日志/缓存返回结果可以把代理理解为私人助理老板客户端有事找某人真实对象先通过助理代理。助理可以在前面挡掉不重要的打扰权限控制也可以在事后记录行程日志老板全程不需要直接接触对方。这个比喻贯穿后面的实现章节方便对照理解。实现代理模式的基本实现分为以下几个步骤定义抽象主题一般是接口或抽象类声明真实主题和代理对象实现的业务方法定义真实主题实现抽象主题中的具体业务定义代理类包含对RealSubject的引用提供和真实主题相同的接口在调用前后添加控制逻辑客户端使用代理// 抽象主题 interface Subject { // 声明同业务对象同名的方法 public void request(); } // 真实主题 class RealSubject implements Subject { public void request() { System.out.println(RealSubject request); } } // 代理类 class Proxy implements Subject { private RealSubject realSubject; Override public void request() { // 访问真实主题之前延迟加载 if (realSubject null) { realSubject new RealSubject(); } // 调用真实主题的方法 realSubject.request(); // 访问真实主题之后可添加日志等逻辑 } }总结代理模式本质上是一层中间人——为真实对象提供一个替身在调用前后添加控制逻辑。什么时候用想控制对某个对象的访问权限、延迟加载、缓存、日志需要为远程对象提供本地代表引入第三方库或遗留代码需要统一调用方式什么时候不用接口差异巨大代理会变得臃肿能修改真实对象源码且代价不大直接修改更简单系统设计阶段就能定义接口规范从源头统一即可简单记忆代理解决控制访问的问题是给真实对象加一层控制。能改源码就改改不了才用代理。代理 vs 装饰器 vs 适配器 vs 外观四个结构型模式都包了一层对象结构相似但意图不同模式接口关系核心意图代理目标接口 被包装对象接口控制访问附加访问前后逻辑装饰器目标接口 被包装对象接口增强功能接口不变适配器目标接口 ≠ 被包装对象接口转换接口让不兼容的类协同外观目标接口是新设计的简化复杂子系统的调用口诀对比代理控访问装饰增功能适配改接口外观简调用。代理模式 vs 中介者模式两者都引入中间层结构相似但意图完全不同维度代理模式中介者模式核心意图控制对单个对象的访问协调多个对象之间的交互对象关系客户端 → 代理 → 真实对象单向委托多个同事对象 ↔ 中介者 ↔ 多个同事对象多向协调封装内容访问控制逻辑权限、延迟加载、缓存对象间的交互规则、通信协议客户端感知客户端不知道真实对象存在各同事对象知道中介者存在但不直接知道其他同事应用场景Spring AOP、MyBatis Mapper、远程调用MVC 框架的 Controller、聊天室服务器、GUI 事件分发用例子说明代理模式你要见 CEO先通过秘书代理。秘书控制访问——过滤不重要的人、安排时间。你只和秘书打交道CEO 对你透明。中介者模式公司的各部门销售、研发、财务不直接相互沟通所有协调通过行政部中介者。销售要研发资源找行政部安排财务要销售数据找行政部转发。各部门知道行政部但不直接依赖其他部门。简单记忆代理管谁能动中介者管怎么联动。代理是单对象的门禁中介者是多对象的调度中心。常见误区误区代理模式 装饰器模式 → 意图不同一个控访问一个增功能误区代理必须和真实对象同接口 → 对这是代理的基本要求否则就不是代理了误区SpringTransactional在同一个类内部调用不生效 → 这是自调用绕过代理的经典坑AOP 代理需要外部调用才能触发拦截逻辑练习题目门禁系统权限控制题目描述某科技园区的门禁系统管理着多个房间每个房间有名称和最低访问权限等级。用户通过门禁终端访问房间门禁终端作为代理会检查用户的权限等级用户权限 ≥ 房间要求的等级 → 放行房间显示欢迎信息用户权限 房间要求的等级 → 拒绝房间不会响应请使用代理模式实现该门禁系统。其中Room真实对象拥有 enter() 方法输出欢迎信息RoomProxy代理在调用 enter() 前进行权限检查只有通过才委托给真实对象输入描述第一行输入一个整数表示用户的权限等级。第二行输入一个整数 N1 ≤ N ≤ 20表示要访问的房间数量。接下来 N 行每行包含房间名称和该房间要求的最低权限等级用空格分隔。输出描述对每个房间通过代理访问后输出权限足够欢迎进入[房间名]权限不足权限不足无法进入[房间名]输入示例2 3 MeetingRoom 1 Lab 3 Office 2输出示例欢迎进入MeetingRoom 权限不足无法进入Lab 欢迎进入Office解题思路代理类RoomProxy实现Room接口内部持有用户权限。在enter()方法中先检查用户权限是否满足房间要求满足则委托给RealRoom.enter()否则直接拒绝。这体现了代理模式的核心——代理在调用真实对象之前增加控制逻辑权限校验对客户端透明。import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int qx sc.nextInt(); int n sc.nextInt(); RoomProxy proxy new RoomProxy(qx); while (n-- 0) { String name sc.next(); int roomQx sc.nextInt(); RealRoom room new RealRoom(name, roomQx); proxy.setRoom(room); proxy.enter(); } } } interface Room { public void enter(); } class RealRoom implements Room { private String name; private int qx; public RealRoom(String name, int qx) { this.name name; this.qx qx; } public void enter() { System.out.println(欢迎进入 this.name); } public String getName() { return this.name; } public int getQx() { return this.qx; } } class RoomProxy implements Room { private int qx; private RealRoom room; public RoomProxy(int qx) { this.qx qx; } public void setRoom(RealRoom room) { this.room room; } public void enter() { if (this.qx room.getQx()) { room.enter(); } else { System.out.println(权限不足无法进入 room.getName()); } } }扩展实际项目中的代理模式Spring AOP 的动态代理Spring AOP 是代理模式最经典的应用。当 Bean 被 AOP 增强时Spring 不会返回原始对象而是返回一个代理对象。代理在方法调用前后插入切面逻辑事务、日志、权限校验等对调用方完全透明。// 业务代码完全感知不到代理的存在 Service public class OrderService { Transactional // 事务由代理自动管理 public void createOrder(Order order) { orderDao.save(order); inventoryService.deduct(order.getProductId(), order.getQty()); } } // Spring 内部创建代理对象JDK 动态代理或 CGLIB // 代理在 createOrder 前后自动开启/提交/回滚事务关键点Spring 默认对实现了接口的 Bean 使用 JDK 动态代理对没有实现接口的 Bean 使用 CGLIB 代理。开发者只需写Transactional、Cacheable等注解代理负责增强逻辑的织入。JDK 动态代理 vs CGLIB 对比