25 设计模式:代理模式-结构型模式

143 阅读5分钟

结构型模式之:

  • 适配器模式
  • 桥接模式
  • 组合模式
  • 装饰模式
  • 外观模式
  • 代理模式
  • 享元模式

1.什么是代理模式?

代理模式是一种结构型设计模式,其目的是通过代理对象控制对另一个对象的访问。代理对象充当了被代理对象的中间人,客户端通过访问代理对象来间接访问被代理对象,从而可以在访问过程中实现一些额外的控制或逻辑。

为什么要控制对于某个对象的访问呢? 举个例子: 有这样一个加载很多高清图片的对象, 你只是偶尔需要使用它, 并且不是每个对象都要使用它。

代理模式解决的问题

你可以实现延迟初始化: 在实际有需要时再创建该对象。 对象的所有客户端都要执行延迟初始代码。 不幸的是, 这很可能会带来很多重复代码。

在理想情况下, 我们希望将代码直接放入对象的类中, 但这并非总是能实现: 比如类可能是第三方封闭库的一部分。

2.使用方式

假设有一个接口 Image,定义了显示图像的操作:

public interface Image { void display(); }

然后有一个实际的图像类 RealImage,用于加载和显示这些高清的图像:

public class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk(filename);
    }

    private void loadFromDisk(String filename) {
        System.out.println("Loading image: " + filename);
    }

    @Override
    public void display() {
        System.out.println("Displaying image: " + filename);
    }
}

现在,我们希望对图像的显示进行控制,例如限制用户对某些图像的访问。我们可以使用代理模式来实现这个控制逻辑。

首先,我们创建一个代理类 ProxyImage 实现 Image 接口:

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) {
            realImage = new RealImage(filename);
        }
        realImage.display();
    }
}

在代理类 ProxyImage 中,当调用 display() 方法时,它会首先检查是否已经创建了 RealImage 对象。如果没有,就会创建一个,并调用 RealImage 对象的 display() 方法来显示图像。

客户端可以直接使用代理对象来加载和显示图像,而无需直接访问实际的图像对象:

public class Client {
    public static void main(String[] args) {
        // 使用代理对象显示图像
        Image image = new ProxyImage("test.jpg");

        // 第一次显示图像
        image.display();
        System.out.println();

        // 第二次显示图像,不需要重新加载
        image.display();
    }
}

通过代理模式,我们可以控制对实际图像对象的访问,并在需要时延迟创建或加载实际的高清图像对象,从而提高系统的性能和安全性。

3.使用场景

从定义和使用场景可以得出,代理模式的产生主要是为了控制访问,来减少相关的多余操作,所以只要是有需要中间层进行控制的场景,都可以用到代理模式,下面是一些常见的场景。

  • 延迟初始化(虚拟代理)。 如果你有一个偶尔使用的重量级服务对象, 一直保持该对象运行会消耗系统资源时, 可使用代理模式。你无需在程序启动时就创建该对象, 可将对象的初始化延迟到真正有需要的时候。

  • 访问控制 (保护代理),如果你只希望特定服务对象,避免被恶意调用, 此时可使用代理模式。 

代理可仅在客户端凭据满足要求时将请求传递给服务对象。

  • 本地执行远程服务(远程代理)。 适用于服务对象位于远程服务器上的情形。  在这种情形中, 代理通过网络传递客户端请求, 负责处理所有与网络相关的复杂细节。

  • 记录日志请求 (日志记录代理)。 适用于当你需要保存对于服务对象的请求历史记录时。  代理可以在向服务传递请求前进行记录。

  • 缓存请求结果 (缓存代理)。 适用于需要缓存客户请求结果并对缓存生命周期进行管理时, 特别是当返回结果的体积非常大时。 

代理可对重复请求所需的相同结果进行缓存, 还可使用请求参数作为索引缓存的键值。

  • 智能引用。 可在没有客户端使用某个重量级对象时立即销毁该对象。 

代理会将所有获取了指向服务对象或其结果的客户端记录在案。 代理会时不时地遍历各个客户端, 检查它们是否仍在运行。 如果相应的客户端列表为空, 代理就会销毁该服务对象, 释放底层系统资源。

代理还可以记录客户端是否修改了服务对象。 其他客户端还可以复用未修改的对象。

通过上面的使用场景,可以发现,大多数跟我们的功能场景不是直接相关,但是多用在项目隔离中,如果你熟悉 Java 语言和 Spring 开发框架,这部分工作都是可以在 Spring AOP 切面中完成的。前面我们也提到,Spring AOP 底层的实现原理就是基于动态代理。

4.优点和缺点

优点:

  • 你可以在客户端毫无察觉的情况下控制服务对象。
  • 如果客户端对服务对象的生命周期没有特殊要求, 你可以对生命周期进行管理。
  • 即使服务对象还未准备好或不存在, 代理也可以正常工作。
  • 开闭原则。 你可以在不对服务或客户端做出修改的情况下创建新代理。

缺点:

  • ** 代码可能会变得复杂, 因为需要新建许多类。
  • ** 服务响应可能会延迟。