urlname
type
Post
password
SyncToConfluence
category
业务技能
date
Apr 2, 2025
slug
6cd43b83-bef2-4bfb-8315-272d3439ec27
icon
Button
catalog
summary
tags
学习笔记
代码质量
专业能力
设计模式
编程基础
cover
Status
BusyTime
Status 1
status
Published

什么是代理模式(Proxy Pattern)

代理模式为另一个对象提供一个代理(Proxy)或占位符(Placeholder),以控制对这个对象的访问。代理控制着对原对象的访问,并允许在将请求提交给原对象之前或之后执行某些操作。
想象一下你想访问一个高度安全的服务器(原对象 RealSubject)。你可能不能直接连接,而是需要通过一个安全网关或防火墙(代理 Proxy)。这个网关会检查你的凭证(访问控制),记录你的访问(日志),甚至可能缓存一些常用数据(缓存)。只有通过了网关的检查,你的请求才会被转发到真正的服务器。这个安全网关就是代理。
代理模式的核心在于引入一个代理对象,作为客户端和目标对象之间的中介,以实现访问控制或其他附加功能。

核心思想

  1. 定义共同接口 (Subject): 定义一个接口,让真实主题 (RealSubject) 和代理 (Proxy) 都实现这个接口。这样,代理就可以在任何需要真实主题的地方被替换使用,客户端无需知道它正在与代理交互。
  1. 创建真实主题 (RealSubject): 实现 Subject 接口,这是真正执行业务逻辑的对象。
  1. 创建代理 (Proxy): 实现 Subject 接口。它内部持有一个对 RealSubject 的引用。代理可以按需创建或获取这个引用。
  1. 控制和委托: 当客户端调用代理对象的方法时,代理可以在将调用委托给 RealSubject 的方法之前或之后执行额外的操作。这些操作是代理模式提供附加价值的地方,例如:
      • 访问控制 (Protection Proxy): 检查客户端是否有权限执行操作。
      • 延迟初始化 (Virtual Proxy): 如果 RealSubject 的创建成本很高,代理可以在第一次需要时才创建它。
      • 日志记录 (Logging Proxy): 记录方法的调用信息。
      • 缓存 (Caching Proxy): 缓存 RealSubject 的操作结果,避免重复计算或请求。
      • 远程代理 (Remote Proxy): 隐藏与位于不同地址空间(如网络另一端)的对象交互的复杂性。

优点

  1. 控制访问 (Access Control): 代理可以对访问真实主题进行精细控制,例如权限检查。
  1. 增加额外职责 (Additional Responsibilities): 可以在不修改真实主题代码的情况下,为其增加如日志、缓存、事务管理等功能(符合开闭原则)。
  1. 延迟加载 (Lazy Loading / Virtual Proxy): 可以推迟重量级对象的创建,直到真正需要时才加载,提高启动性能或节省资源。
  1. 远程访问透明化 (Remote Proxy): 使客户端像访问本地对象一样访问远程对象,隐藏网络通信细节。
  1. 解耦 (Decoupling): 客户端代码与真实主题解耦,只依赖于 Subject 接口。

可能的缺点

  1. 增加间接层: 引入代理对象会增加系统的间接性,可能导致理解上的复杂性。
  1. 潜在的性能开销: 每次调用都需要通过代理进行转发,可能会带来微小的性能开销(尽管通常可以被缓存或延迟加载等带来的好处所抵消)。
  1. 可能需要额外的类: 系统中可能会增加一些代理类。

使用场景

  1. 延迟初始化 (Virtual Proxy): 当对象的创建或加载非常耗时或消耗资源时,例如加载大图片、初始化复杂的组件。
  1. 访问控制 (Protection Proxy): 当需要根据不同的权限级别控制对对象方法的访问时。
  1. 远程对象访问 (Remote Proxy): 在分布式系统中,一个对象需要代表另一个地址空间中的对象(例如 Android 中的 Binder 机制)。
  1. 日志记录/审计 (Logging Proxy): 在方法调用前后记录日志信息。
  1. 缓存 (Caching Proxy): 缓存昂贵操作的结果,以便后续快速返回。
  1. 智能引用 (Smart Reference): 在访问对象时执行一些附加操作,如引用计数、加锁等。
  1. Android 中的场景:
      • 图片加载库 (如 Glide, Picasso): 它们在内部广泛使用了类似虚拟代理和缓存代理的思想。当你请求加载图片时,库可能会先返回一个占位符(或从内存缓存/磁盘缓存返回),同时在后台线程加载真实的高清图片,加载完成后再更新 ImageView。
      • Lazy<T> 委托属性: Kotlin 标准库中的 by lazy { ... } 实际上就是一种虚拟代理的实现,它推迟了大括号内代码块的执行(即对象的创建),直到该属性第一次被访问。
      • 网络请求缓存: 虽然 OkHttp 的 Interceptor 更灵活,但可以构想一个 CachingApiProxy 来包装 Retrofit 的 Service 接口,优先从缓存中获取数据。
      • 权限检查代理: 创建一个代理来包装需要特定权限才能访问的服务或数据源,代理在调用真实方法前检查权限。
      • Android Binder (AIDL): 这是典型的远程代理。客户端持有的 Service 接口实际上是一个代理对象(Proxy),它负责将方法调用打包并通过 Binder 驱动发送给运行在另一个进程中的真实 Service 对象(Stub)。

UML 图

下面是基于“图片加载(虚拟代理)”示例的代理模式 UML 图:
  • Image (Subject): 定义了图片对象的通用接口 display()。
  • RealImage (RealSubject): 实现了 Image 接口。它负责从磁盘加载图片(耗时操作)并显示。
  • ProxyImage (Proxy): 实现了 Image 接口。它持有 RealImage 的引用(初始为 null)和文件名。当 display() 被调用时,它检查 realImage 是否已创建,如果没有,则创建 RealImage 实例(触发加载),然后调用 realImage.display()。
  • Client: 通过 Image 接口与 ProxyImage 交互,无需关心图片是何时加载的。

代码示例

在这个例子中:
  • Image 是公共接口。
  • RealImage 是真实主题,它的构造函数模拟了耗时的加载操作。
  • ProxyImage 是代理,它实现了 Image 接口,但只有在 display() 首次被调用时才创建 RealImage 实例。
  • 客户端代码创建了 ProxyImage 对象。注意,仅仅创建代理对象并不会触发图片加载。只有当 display() 被调用时,加载过程才开始(如果是第一次调用)。后续对同一个代理实例调用 display() 则直接显示,不再重新加载。

总结

  • 代理模式通过引入一个代理对象来控制对真实对象的访问
  • 它可以在不改变真实对象代码的情况下,为其添加额外的控制逻辑或功能(如权限检查、延迟加载、日志、缓存)。
  • 根据其目的,可以分为虚拟代理、保护代理、远程代理、缓存代理等多种形式。
  • 在 Android 开发中,尤其是在处理资源加载、权限控制、网络通信和进程间通信(Binder)时非常有用。
💡
记住它的核心:想见我(RealSubject)?先通过我的代理(Proxy)再说!
设计模式——结构性-适配器模式设计模式——结构型-外观模式
Loading...