基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

设计模式--代理模式 (Proxy)

知识点图片

本文详细解析了代理设计模式,即为对象提供代理以控制其访问。内容涵盖其核心思想、结构、代码示例、优缺点,并与装饰者模式对比,展示了真实应用。

我们来详细探讨一下设计模式中的代理模式(Proxy Pattern)

这是一个非常常用且重要的结构型设计模式。我会从以下几个方面为你深入浅出地讲解:

  1. 核心思想与生活中的比喻
  2. 为什么需要代理模式?(意图)
  3. 代理模式的结构与参与者
  4. 常见的代理模式类型
  5. 代码示例(Java)
  6. 优缺点分析
  7. 与装饰者模式的比较
  8. 真实世界的应用场景

1. 核心思想与生活中的比喻

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

简单来说,代理模式就是在客户端和目标对象之间增加一个中间层(代理对象)。客户端不直接访问目标对象,而是通过访问代理对象来间接访问。这个代理对象可以在访问前后执行一些额外的操作。

生活中的比喻:

  • 海外代购/房产中介: 你想买一个国外的商品,但直接购买流程复杂、运费高。于是你找了一个代购,你把需求告诉代购,由代购去处理购买、清关、邮寄等所有复杂事宜,最后你只需要从代购手中拿到商品即可。这个“代购”就是你的代理。
  • 明星经纪人: 你想找一位明星拍广告,你不会直接联系明星本人,而是联系他的经纪人。经纪人会帮你处理合同、档期、酬劳等事宜,过滤掉不必要的骚扰。经纪人就是明星的代理。

在这些比喻中,代理(代购、经纪人)控制了对真实对象(海外商品、明星)的访问,并在此过程中附加了一些自己的处理逻辑。


2. 为什么需要代理模式?(意图)

代理模式主要解决以下问题:

  • 远程访问(Remote Proxy): 当一个对象在不同的地址空间中时(例如在另一台服务器上),代理可以在本地代表这个远程对象,隐藏网络通信的复杂性。
  • 延迟初始化(Virtual Proxy): 当一个对象的创建成本很高时(例如,加载一张高清大图、初始化一个数据库连接),可以使用代理来推迟它的创建,直到真正需要使用它的时候才创建。
  • 访问控制(Protection Proxy): 代理可以根据不同的权限,决定是否允许客户端访问真实对象。例如,不同用户级别的权限控制。
  • 附加功能(Smart Proxy): 代理可以在访问真实对象前后增加额外的功能,如日志记录、性能监控、事务管理、缓存等,而无需修改真实对象的代码。

3. 代理模式的结构与参与者

代理模式通常包含以下三个核心角色:

Proxy Pattern UML Diagram

  1. Subject(抽象主题)

    • 定义了真实主题(RealSubject)和代理(Proxy)的共同接口。
    • 这样一来,任何使用真实主题的地方都可以使用代理,客户端无需关心自己使用的是真实对象还是代理对象。
    • 通常是一个接口或抽象类。
  2. RealSubject(真实主题)

    • 也称为“被代理对象”,是代理所代表的真实实体。
    • 它实现了 Subject 接口,是最终执行业务逻辑的对象。
  3. 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 加密等功能。
00:00
00:00