代理设计模式是一种结构模式,它为 另一个对象提供占位符或代理,允许您控制对它的访问。
在实际应用程序中,您经常使用资源密集型、远程或敏感组件,例如数据库连接、第三方 API、文件系统或大型内存数据集。

有时您还想:
延迟或控制对实际实现的访问
添加额外的功能(例如日志记录、身份验证),而无需修改现有代码。
代理位于客户端和真实对象之间,拦截调用并可选择更改行为。

让我们通过一个真实世界的示例,看看如何应用代理模式来构建与昂贵或敏感资源的更安全、更智能、更可控的交互。
问题:急切加载
想象一下,您正在构建一个图片库应用程序。用户可以滚动浏览图像缩略图列表,当他们单击一个缩略图时, 会显示完整的高分辨率图像。
-
- 图像界面
我们定义了一个基本 接口来支持渲染行为:Image
public interface Image {
void display();
String getFileName();
} - 图像界面
-
- 高分辨率图像实现
此类表示加载和呈现的实际全尺寸图像。在构建它时,它会加载完整的图像数据------这故意缓慢且占用大量内存。
public class HighResolutionImage implements Image {
private String fileName;
private byte[] imageData; // Simulate large datapublic 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; }
}
- 高分辨率图像实现
-
- 天真的画廊应用程序
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."); }
}
这种方法有什么问题?
-
- . 资源密集型初始化
Every 在构建时加载其图像数据,即使用户从未查看过图像。这导致:HighResolutionImage
应用程序启动缓慢
不必要的内存消耗
浪费的 I/O 带宽
如果您的图库显示数十或数百个缩略图,这种方法很快就会成为瓶颈。
- . 资源密集型初始化
-
- 无法控制访问
如果您想:
每次实际显示图像时都记录?
在加载敏感图像之前添加权限检查?
缓存以前加载的图像以供重用?
- 无法控制访问
现在,您必须直接修改 类------混合职责、打破单一责任原则,或者更糟糕的是,跨客户端复制逻辑。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
保护代理 -- 根据用户角色或权限限制访问。
日志记录代理 -- 记录访问尝试和使用元数据。
-
- 添加保护代理
保护代理根据授权规则控制对敏感作的访问。例如,只有具有角色的用户才能 查看机密图像。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
-
- 添加日志记录代理
日志记录代理拦截方法调用,并在将其转发到真实对象之前或之后记录它们。这对于审计、调试或使用情况分析特别有用。
- 添加日志记录代理
以下是我们如何增强原始 方法: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