1. 设计模式之代理模式:不修改原始对象的情况下,添加额外功能

1.1. 介绍

代理模式(Proxy Pattern)是一种结构型设计模式,它通过创建一个代理对象来控制对原始对象的访问。

代理对象可以在调用原始对象的方法前后添加额外的逻辑(如权限检查、缓存、日志记录等),而无需修改原始对象的代码。

代理模式解决的是在直接访问某些对象时可能遇到的问题,例如对象创建成本高、需要安全控制或远程访问等。

类型 作用 典型应用场景
静态代理 手动编写代理类,直接引用原始对象。 简单场景,代理逻辑固定。
动态代理 运行时动态生成代理类(如JDK动态代理、CGLIB)。 AOP(如Spring的@Transactional)。
虚拟代理 延迟加载原始对象(如大图片的按需加载)。 资源密集型对象的优化。

1.2. 优缺点及建议

优点

  1. 职责分离:代理模式将访问控制与业务逻辑分离。代理对象作为原始对象的“替身”,控制客户端对原始对象的访问。
  2. 扩展性:可以灵活地添加额外的功能或控制。在不修改原始对象的情况下,通过代理对象添加额外功能(如缓存、延迟加载、权限校验等)。
  3. 解耦:客户端只需与代理交互,无需直接依赖原始对象。

缺点

  1. 性能开销:增加了代理层可能会影响请求的处理速度。
  2. 实现复杂性:某些类型的代理模式实现起来可能较为复杂。

使用建议

  1. 根据具体需求选择合适的代理类型,如远程代理、虚拟代理、保护代理等。
  2. 确保代理类与真实对象接口一致,以便客户端透明地使用代理。
  3. 如果代理逻辑固定且简单,用静态代理。
  4. 如果需要动态扩展或AOP,用动态代理。

注意事项

  • 与适配器模式的区别:适配器模式改变接口,而代理模式不改变接口。
  • 与装饰器模式的区别:装饰器模式用于增强功能,代理模式用于控制访问。
对比维度 代理模式 装饰器模式
目的 控制访问,增强非功能性逻辑(如权限、缓存)。 动态扩展对象的功能(如加糖、加冰)。
关系 代理类和原始类实现同一接口,但代理类知道原始类的存在。 装饰器和原始类实现同一接口,可嵌套多层。
典型应用 远程调用、延迟加载、AOP。 IO流(如BufferedInputStream)。

1.3. 实现及相关代码

1.3.1. 静态代理实现

1. 定义接口(抽象主题)

// 抽象主题(接口)
public interface Image {
    void display();
}

2. 实现真实主题(原始对象)

// 真实主题(原始对象)
public class RealImage implements Image {
    private String filename;

    public RealImage(String filename) {
        this.filename = filename;
        loadFromDisk(); // 模拟耗时操作
    }

    private void loadFromDisk() {
        System.out.println("加载图片: " + filename);
    }

    @Override
    public void display() {
        System.out.println("显示图片: " + filename);
    }
}

3. 实现代理类(控制访问)

// 代理类(控制对RealImage的访问)
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();
    }
}

4. 客户端调用

public class Client {
    public static void main(String[] args) {
        Image image = new ProxyImage("test.jpg");
        image.display(); // 第一次调用会加载图片
        image.display(); // 第二次直接显示(已缓存)
    }
}

输出结果

加载图片: test.jpg
显示图片: test.jpg
显示图片: test.jpg

1.3.2. 动态代理(JDK实现)

动态代理无需手动编写代理类,而是在运行时动态生成。

1. 定义InvocationHandler

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

public class ImageInvocationHandler implements InvocationHandler { 
    // 原始对象
    private Object target;

    public ImageInvocationHandler(Object target) {
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理前置操作(如权限校验)");
        // 调用原始方法
        Object result = method.invoke(target, args);
        System.out.println("代理后置操作(如日志记录)");
        return result;
    }
}

2. 生成动态代理对象

import java.lang.reflect.Proxy;

public class Client {
    public static void main(String[] args) {
        RealImage realImage = new RealImage("test.jpg");
        Image proxyImage = (Image) Proxy.newProxyInstance(
            realImage.getClass().getClassLoader(),
            realImage.getClass().getInterfaces(),
            new ImageInvocationHandler(realImage)
        );
        proxyImage.display();
    }
}

输出结果

代理前置操作(如权限校验)
加载图片: test.jpg
显示图片: test.jpg
代理后置操作(如日志记录)

1.4. 代理模式的应用场景

  1. 远程代理(如RPC调用)
    • 客户端通过代理访问远程服务(如Dubbo的Stub)。
  2. 虚拟代理(延迟加载)
    • 按需加载大资源(如图片、文件)。
  3. 保护代理(权限控制)
    • 在访问敏感对象前进行权限校验。
  4. 缓存代理
    • 缓存原始对象的结果(如Redis缓存代理数据库查询)。
  5. AOP编程
    • Spring的@Transactional@Cacheable等基于动态代理实现。

1.5. 总结

  • 核心价值:通过代理对象控制对原始对象的访问,实现解耦和功能增强。
  • 静态代理:简单直接,但需手动编写代理类。
  • 动态代理:灵活(JDK/CGLIB),适合AOP场景。
  • 适用场景:权限控制、缓存、延迟加载、日志记录等。

results matching ""

    No results matching ""