代理设计模式

代理设计模式是一种结构模式,它为 另一个对象提供占位符或代理,允许您控制对它的访问。

在实际应用程序中,您经常使用资源密集型、远程或敏感组件,例如数据库连接、第三方 API、文件系统或大型内存数据集。

有时您还想:

延迟或控制对实际实现的访问

添加额外的功能(例如日志记录、身份验证),而无需修改现有代码。

代理位于客户端和真实对象之间,拦截调用并可选择更改行为。

让我们通过一个真实世界的示例,看看如何应用代理模式来构建与昂贵或敏感资源的更安全、更智能、更可控的交互。

问题:急切加载

想象一下,您正在构建一个图片库应用程序。用户可以滚动浏览图像缩略图列表,当他们单击一个缩略图时, 会显示完整的高分辨率图像。

    1. 图像界面
      我们定义了一个基本 接口来支持渲染行为:Image

    public interface Image {
    void display();
    String getFileName();
    }

    1. 高分辨率图像实现
      此类表示加载和呈现的实际全尺寸图像。在构建它时,它会加载完整的图像数据------这故意缓慢且占用大量内存。

    public class HighResolutionImage implements Image {
    private String fileName;
    private byte[] imageData; // Simulate large data

    复制代码
     public HighResolutionImage(String fileName) {
         this.fileName = fileName;
         loadImageFromDisk(); // Expensive operation!
     }
    
     private void loadImageFromDisk() {
         System.out.println("Loading image: " + fileName + " from disk (Expensive Operation)...");
         // Simulate disk read and memory allocation
         try {
             Thread.sleep(2000); // Simulate delay
             this.imageData = new byte[10 * 1024 * 1024]; // 10MB
         } catch (InterruptedException e) {
             Thread.currentThread().interrupt();
         }
         System.out.println("Image " + fileName + " loaded successfully.");
     }
    
     @Override
     public void display() {
         System.out.println("Displaying image: " + fileName);
         // Actual rendering logic would go here
     }
    
     @Override
     public String getFileName() {
         return fileName;
     }

    }

    1. 天真的画廊应用程序

    public class ImageGalleryAppV1 {
    public static void main(String[] args) {
    System.out.println("Application Started. Initializing images for gallery...");

    复制代码
         // Imagine we need to create image objects for a list of thumbnails
         // Even if the user never clicks them, they get loaded!
         Image image1 = new HighResolutionImage("photo1.jpg");
         Image image2 = new HighResolutionImage("photo2.png");
         Image image3 = new HighResolutionImage("photo3.gif");
    
         System.out.println("\nGallery initialized. User might view an image now.");
    
         // User clicks on image1
         System.out.println("User requests to display " + image1.getFileName());
         image1.display();
    
         // User clicks on image3
         System.out.println("\nUser requests to display " + image3.getFileName());
         image3.display();
    
         // image2 was loaded but never displayed by the user in this session. Waste of resources!
         System.out.println("\nApplication finished.");
     }    

    }

这种方法有什么问题?

    1. . 资源密集型初始化
      Every 在构建时加载其图像数据,即使用户从未查看过图像。这导致:HighResolutionImage
      应用程序启动缓慢
      不必要的内存消耗
      浪费的 I/O 带宽
      如果您的图库显示数十或数百个缩略图,这种方法很快就会成为瓶颈。
    1. 无法控制访问
      如果您想:
      每次实际显示图像时都记录?
      在加载敏感图像之前添加权限检查?
      缓存以前加载的图像以供重用?

现在,您必须直接修改 类------混合职责、打破单一责任原则,或者更糟糕的是,跨客户端复制逻辑。HighResolutionImage

我们真正需要什么

我们需要一个解决方案,使我们能够:

将昂贵的图像数据加载推迟到实际需要时才进行。

添加额外的行为,如日志记录、访问控制或缓存,而无需更改现有 类。HighResolutionImage

维护相同的接口,以便不需要更改客户端代码。

这就是代理设计模式发挥作用的地方。

代理模式

代理设计模式为另一个对象 提供了一个替代对象或占位符来控制对它的访问。客户端不是直接与"真实"对象(例如,)交互,而是与实现相同接口的代理交互。HighResolutionImage

这允许代理执行额外的职责,例如延迟初始化、访问控制、日志记录或缓存,而无需更改原始类或客户端代码。

类图

主题(例如,Image)

定义 RealSubject 和 Proxy 共享的通用作的接口或抽象类。

这种抽象允许客户端可以互换地处理真实对象及其代理。

实主体(例如HighResolutionImage)

执行实际工作的 实际对象。

通常创建成本高昂,使用资源密集型,或者需要额外的控制层。

代理表示此对象并管理对它的访问。

代理(例如ImageProxy)

实现与 (即) 相同的接口,使其能够无缝地站进。RealSubjectImage

保存对真实对象的引用,并控制创建或访问它的时间和方式。

充当看门人,仅在适当的时候将调用委托给真实对象。

客户(例如ImageGalleryApp)

通过界面处理对象 。Subject

不需要知道它是与真实对象还是代理交互。

这种抽象使客户端代码保持干净、解耦和适应性强。

根据用例,代理可能采用不同的形式:

虚拟代理:推迟真实对象的创建,直到实际需要(延迟加载)。

保护代理:在允许访问某些作之前执行权限检查。

远程代理:处理网络上本地和远程对象之间的通信。

缓存代理: 高速缓存昂贵的结果,并避免重复调用实际主题。

智能代理:添加日志记录、引用计数或方法调用前后监控。

实现代理

现在让我们重构我们的图像库以使用代理设计模式。

我们不会急切地加载每个 ,而是使用一个代理来包装它并延迟加载,直到真正需要图像。HighResolutionImage

1:创建代理类

我们将实现 与 相同的接口,允许客户端互换使用它。在内部,它将保存对真实图像的引用,但仅在需要时初始化它。ImageProxyImageHighResolutionImage

复制代码
public class ImageProxy implements Image {
    private String fileName;
    private HighResolutionImage realImage; // RealSubject

    public ImageProxy(String fileName) {
        this.fileName = fileName;
        System.out.println("ImageProxy: Created for " + fileName + ". Real image not loaded yet.");
    }

    @Override
    public String getFileName() {
        // Can safely return without loading the image
        return fileName;
    }

    @Override
    public void display() {
        // Lazy initialization: Load only when display() is called
        if (realImage == null) {
            System.out.println("ImageProxy: display() requested for " + fileName + ". Loading high-resolution image...");
            realImage = new HighResolutionImage(fileName);
        } else {
            System.out.println("ImageProxy: Using cached high-resolution image for " + fileName);
        }

        // Delegate the display call to the real image
        realImage.display();
    }
}

2:更新客户端代码以使用代理

从客户端的角度来看,没有任何变化------它仍然与 .但现在,它不再预先处理重量级对象,而是获得一个轻量级代理,仅按需加载真实对象。Image

复制代码
public class ImageGalleryAppV2 {
    public static void main(String[] args) {
        System.out.println("Application Started. Initializing image proxies for gallery...");

        // Create lightweight proxies instead of full image objects
        Image image1 = new ImageProxy("photo1.jpg");
        Image image2 = new ImageProxy("photo2.png"); // Never displayed
        Image image3 = new ImageProxy("photo3.gif");

        System.out.println("\nGallery initialized. No images actually loaded yet.");
        System.out.println("Image 1 Filename: " + image1.getFileName()); // Does not trigger image load

        // User clicks on image1
        System.out.println("\nUser requests to display " + image1.getFileName());
        image1.display(); // Lazy loading happens here

        // User clicks on image1 again
        System.out.println("\nUser requests to display " + image1.getFileName() + " again.");
        image1.display(); // Already loaded; no loading delay

        // User clicks on image3
        System.out.println("\nUser requests to display " + image3.getFileName());
        image3.display(); // Triggers loading for image3

        System.out.println("\nApplication finished. Note: photo2.png was never loaded.");
    }
}

我们收获了什么?

让我们在这里回顾一下使用代理模式的优势:

延迟加载:仅在用户查看图像时才加载图像,从而缩短启动时间和内存使用。

干净的接口:客户端代码统一交互 ,不知道它是处理真实对象还是代理。Image

对真实对象没有代码更改:原始内容 保持不变。我们不必修改它来支持延迟加载。HighResolutionImage

可重用性: 可以重复用于其他优化,如日志记录、缓存或访问控制。ImageProxy

这是虚拟代理的完美用例 ------一种代理,它代表一个昂贵的对象,并将其创建推迟到绝对必要时。

通过仅引入一个小类 (),我们提高了性能、节省了内存,并保持了我们的设计干净、可扩展且易于维护------所有这些都无需触及原始图像类或更改客户端逻辑。ImageProxy

使用其他代理类型进行扩展

代理模式最强大的方面之一是 它可以很容易地扩展以支持不同的关注点(通常是同时的),而无需修改真实对象或显着更改客户端代码。

让我们探讨如何发展我们 不仅充当虚拟代理,而且充当:ImageProxy

保护代理 -- 根据用户角色或权限限制访问。

日志记录代理 -- 记录访问尝试和使用元数据。

    1. 添加保护代理
      保护代理根据授权规则控制对敏感作的访问。例如,只有具有角色的用户才能 查看机密图像。ADMIN

我们可以将方法扩展 为 在加载或显示图像之前检查访问权限:display()ImageProxy

复制代码
private boolean checkAccess(String userRole) {
        System.out.println("ProtectionProxy: Checking access for role: " + userRole + " on file: " + fileName);
        // Simulate a basic access control rule
        return "ADMIN".equals(userRole) || !fileName.contains("secret");
    }

    public void display(String userRole) {
        if (!checkAccess(userRole)) {
            System.out.println("ProtectionProxy: Access denied for " + fileName);
            return;
        }

        if (realImage == null) {
            System.out.println("ImageProxy: Loading image for authorized access....");
            realImage = new HighResolutionImage(fileName);
        }

        realImage.display();
    }

这种方法保持 了自由授权逻辑,同时在代理级别启用了细粒度的访问控制。HighResolutionImage

    1. 添加日志记录代理
      日志记录代理拦截方法调用,并在将其转发到真实对象之前或之后记录它们。这对于审计、调试或使用情况分析特别有用。

以下是我们如何增强原始 方法:display()

复制代码
@Override
public void display() {
    System.out.println("LoggingProxy: Attempting to display " + fileName + " at " + new java.util.Date());

    if (realImage == null) {
        System.out.println("ImageProxy: Lazy-loading image...");
        realImage = new HighResolutionImage(fileName);
    }

    realImage.display();

    System.out.println("LoggingProxy: Finished displaying " + fileName + " at " + new java.util.Date());
}

只需几行额外的代码, 现在就可以执行多个角色:ImageProxy

充当昂贵对象(虚拟代理)的替代品

延迟初始化直到需要(延迟加载)

限制对敏感内容的访问(保护代理)

记录映像访问和使用模式(日志记录代理)

所有这些都是透明地发生的,客户端或真实对象都不知道。

其他阅读材料:

复制代码
https://pan.baidu.com/s/1c1oQItiA7nZxz8Rnl3STpw?pwd=yftc
https://pan.quark.cn/s/dec9e4868381
相关推荐
毕设源码尹学长21 分钟前
计算机毕业设计 java 血液中心服务系统 基于 Java 的血液管理平台Java 开发的血液服务系统
java·开发语言·课程设计
lumi.43 分钟前
2.3零基础玩转uni-app轮播图:从入门到精通 (咸虾米总结)
java·开发语言·前端·vue.js·微信小程序·uni-app·vue
mask哥1 小时前
详解flink SQL基础(四)
java·大数据·数据库·sql·微服务·flink
灰原喜欢柯南1 小时前
Spring Boot 自动配置全流程深度解析
java·spring boot·后端
Code_Artist1 小时前
[Java并发编程]4.阻塞队列
java·数据结构·后端
心月狐的流火号2 小时前
Java NIO Selector 源码分析
java
MrSYJ2 小时前
AuthenticationEntryPoint认证入口
java·spring cloud·架构
lssjzmn3 小时前
Java并发容器ArrayBlockingQueue与LinkedBlockingQueue对比PK
java·消息队列
再学一点就睡3 小时前
手撕前端常用 7 种设计模式:从原理到实战,附完整代码案例
前端·设计模式