设计模式--代理模式 (Proxy)
本文详细解析了代理设计模式,即为对象提供代理以控制其访问。内容涵盖其核心思想、结构、代码示例、优缺点,并与装饰者模式对比,展示了真实应用。
我们来详细探讨一下设计模式中的代理模式(Proxy Pattern)。
这是一个非常常用且重要的结构型设计模式。我会从以下几个方面为你深入浅出地讲解:
- 核心思想与生活中的比喻
- 为什么需要代理模式?(意图)
- 代理模式的结构与参与者
- 常见的代理模式类型
- 代码示例(Java)
- 优缺点分析
- 与装饰者模式的比较
- 真实世界的应用场景
1. 核心思想与生活中的比喻
核心思想: 为其他对象提供一种代理以控制对这个对象的访问。
简单来说,代理模式就是在客户端和目标对象之间增加一个中间层(代理对象)。客户端不直接访问目标对象,而是通过访问代理对象来间接访问。这个代理对象可以在访问前后执行一些额外的操作。
生活中的比喻:
- 海外代购/房产中介: 你想买一个国外的商品,但直接购买流程复杂、运费高。于是你找了一个代购,你把需求告诉代购,由代购去处理购买、清关、邮寄等所有复杂事宜,最后你只需要从代购手中拿到商品即可。这个“代购”就是你的代理。
- 明星经纪人: 你想找一位明星拍广告,你不会直接联系明星本人,而是联系他的经纪人。经纪人会帮你处理合同、档期、酬劳等事宜,过滤掉不必要的骚扰。经纪人就是明星的代理。
在这些比喻中,代理(代购、经纪人)控制了对真实对象(海外商品、明星)的访问,并在此过程中附加了一些自己的处理逻辑。
2. 为什么需要代理模式?(意图)
代理模式主要解决以下问题:
- 远程访问(Remote Proxy): 当一个对象在不同的地址空间中时(例如在另一台服务器上),代理可以在本地代表这个远程对象,隐藏网络通信的复杂性。
- 延迟初始化(Virtual Proxy): 当一个对象的创建成本很高时(例如,加载一张高清大图、初始化一个数据库连接),可以使用代理来推迟它的创建,直到真正需要使用它的时候才创建。
- 访问控制(Protection Proxy): 代理可以根据不同的权限,决定是否允许客户端访问真实对象。例如,不同用户级别的权限控制。
- 附加功能(Smart Proxy): 代理可以在访问真实对象前后增加额外的功能,如日志记录、性能监控、事务管理、缓存等,而无需修改真实对象的代码。
3. 代理模式的结构与参与者
代理模式通常包含以下三个核心角色:
Subject(抽象主题)
- 定义了真实主题(RealSubject)和代理(Proxy)的共同接口。
- 这样一来,任何使用真实主题的地方都可以使用代理,客户端无需关心自己使用的是真实对象还是代理对象。
- 通常是一个接口或抽象类。
RealSubject(真实主题)
- 也称为“被代理对象”,是代理所代表的真实实体。
- 它实现了
Subject接口,是最终执行业务逻辑的对象。
Proxy(代理)
- 也实现了
Subject接口,所以能替代真实主题。 - 内部持有一个
RealSubject对象的引用。 - 它负责控制对
RealSubject的访问,并可以在调用真实主题的方法前后执行预处理和后处理操作。
- 也实现了
4. 常见的代理模式类型
根据代理的目的,可以分为以下几种常见的类型:
- 虚拟代理(Virtual Proxy): 根据需要创建开销大的对象。例如,只有当用户点击图片时,才真正从网络加载这张图片。
- 远程代理(Remote Proxy): 为一个位于不同地址空间的对象提供一个本地的代理。例如,Java RMI(远程方法调用)。
- 保护代理(Protection Proxy): 控制对原始对象的访问。保护代理用于对象应该有不同访问权限的情况。
- 智能代理(Smart Proxy): 在访问对象时执行一些附加操作,例如引用计数、线程安全检查等。
此外,根据代理类的创建时机,还可以分为:
- 静态代理: 代理类在编译时就已经创建好了,我们手动为每个真实类编写一个代理类。
- 动态代理: 代理类是在程序运行时动态生成的。我们不需要为每个真实类都手动创建一个代理类。Java 的
java.lang.reflect.Proxy和 CGLIB 库是实现动态代理的常用方式。动态代理在 Spring AOP 等框架中被广泛使用。
5. 代码示例(Java - 虚拟代理)
我们用一个加载图片的场景来演示虚拟代理。假设加载一张大图非常耗时,我们希望在图片真正需要显示时才加载它。
步骤 1: 定义抽象主题 (Subject)
java
// Subject: 图片接口
public interface Image {
void display();
}
步骤 2: 创建真实主题 (RealSubject)
java
// RealSubject: 真实图片类,负责从磁盘加载图片
public class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk();
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename + " from disk...");
// 模拟耗时操作
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}
步骤 3: 创建代理 (Proxy)
java
// Proxy: 图片代理类
public class ProxyImage implements Image {
private RealImage realImage; // 持有真实对象的引用
private String filename;
public ProxyImage(String filename) {
this.filename = filename;
}
@Override
public void display() {
// 当真正需要显示时,才创建和加载真实图片对象
if (realImage == null) {
System.out.println("Proxy: Creating RealImage object for the first time.");
realImage = new RealImage(filename);
}
// 调用真实对象的方法
realImage.display();
}
}
步骤 4: 客户端调用
java
// Client: 客户端
public class Client {
public static void main(String[] args) {
// 创建代理对象,此时真实对象还未创建
Image image = new ProxyImage("photo.jpg");
// 第一次调用 display()
System.out.println("First call to display():");
image.display();
System.out.println("\n--------------------------\n");
// 第二次调用 display()
System.out.println("Second call to display():");
image.display();
}
}
运行结果:
plaintext
First call to display():
Proxy: Creating RealImage object for the first time.
Loading image: photo.jpg from disk...
Displaying image: photo.jpg
--------------------------
Second call to display():
Displaying image: photo.jpg
分析:
- 客户端自始至终只和
Image接口和ProxyImage对象打交道。 RealImage对象(那个耗时的加载过程)只在第一次调用display()方法时才被创建。- 第二次调用
display()时,直接使用了已经创建好的realImage对象,没有再次执行加载操作,从而提升了性能。
6. 优缺点分析
优点:
- 职责清晰: 真实主题只需关注业务逻辑,非业务功能(如权限、日志)由代理来完成。
- 高扩展性: 可以在不修改真实主题的情况下,增加新的功能。符合开闭原则。
- 中介隔离作用: 代理可以在客户端和目标对象之间起到保护作用,过滤掉不合法的请求。
缺点:
- 增加复杂性: 引入了代理类,可能会导致系统中的类数量增加。
- 性能影响: 由于在客户端和真实主题之间增加了代理,请求的处理速度可能会有轻微的延迟。
7. 与装饰者模式的比较
代理模式和装饰者模式在结构上非常相似,但它们的意图完全不同。
- 代理模式(Proxy): 核心在于控制对对象的访问。代理通常自己管理其代表的真实对象的生命周期(例如,虚拟代理自己创建真实对象)。
- 装饰者模式(Decorator): 核心在于增强对象的功能。装饰者不关心对象的创建,它只是为已经存在的对象动态添加新的职责。一个对象可以被多个装饰者层层包裹。
可以这样理解:代理模式是“你办事,我放心,但得听我的”,而装饰者模式是“你还是你,但我给你穿上了一件新衣服”。
8. 真实世界的应用场景
- Spring AOP(面向切面编程): Spring AOP 的底层实现就是基于动态代理。通过为目标 Bean 创建代理,Spring 可以在方法调用前后织入(Weave)各种“切面”逻辑,如事务管理 (
@Transactional)、日志记录、安全控制等。 - RPC 框架(如 Dubbo、gRPC): 客户端调用远程服务时,实际是与一个本地的代理对象(Stub)交互,这个代理负责网络通信、序列化和反序列化,使得调用远程服务就像调用本地方法一样简单。
- ORM 框架中的懒加载(Lazy Loading): 像 Hibernate/JPA 这类框架,当你查询一个对象时,它关联的其他对象可能不会立即从数据库加载,而是返回一个代理对象。只有当你第一次访问这个关联对象时,代理才会触发真正的数据库查询。这正是虚拟代理的应用。
- Web 服务器中的请求过滤: 像 Nginx 这样的反向代理服务器,可以作为 Web 应用的代理,提供负载均衡、缓存、SSL 加密等功能。
右滑查看面试常问