代理模式(Proxy Pattern)详解
一、代理模式简介
代理模式(Proxy Pattern) 是一种 结构型设计模式(对象结构型模式),它为其他对象提供一个代理以控制对这个对象的访问。简而言之,代理模式就是在不改变原始类代码的前提下,通过引入一个代理类来间接地访问目标对象。
引入一个新的代理对象
代理对象在客户端对象和目标对象之间起到中介的作用
去掉客户不能看到的内容和服务或者增添客户需要的额外的新服务
代理模式可以用来实现多种功能,比如延迟初始化、权限检查、日志记录等。
生活案例中的例子:
生活案例中的例子:代购
顾客 ——> 代购网站 ——> 商品
软件中:
客户端 ——> 代理对象 ——> 真实对象
代理模式包含以下3个角色:
Subject(抽象主题角色)
Proxy(代理主题角色)
RealSubject(真实主题角色)
二、解决的问题类型
代理模式主要用于以下几种情况:
- 远程代理(Remote Proxy):为一个位于不同的地址空间的对象提供一个本地的代理对象,这个不同的地址空间可以在同一台主机中,也可以在另一台主机中,远程代理又称为大使(Ambassador)
- 虚拟代理(Virtual Proxy):根据需要创建开销很大的对象,从而提高性能。例如,图像加载器可能在图片完全加载前显示一个占位符。(如果需要创建一个资源消耗较大的对象,先创建一个消耗相对较小的对象来表示,真实对象只在需要时才会被真正创建)
- 保护代理(Protect Proxy):基于调用者身份控制对原始对象的访问权限。控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限
- 智能引用代理(Smart Reference Proxy):当对象被访问时,执行额外的操作,如计数访问次数或验证权限。
- 缓冲代理(Cache Proxy):为某一个目标操作的结果提供临时的存储空间,以便多个客户端可以共享这些结果
三、使用场景
- 延迟加载(Lazy Loading):仅在实际需要时才加载资源。
- 缓存:提供临时存储以减少重复计算或数据获取。(缓冲代理)
- 访问控制:限制对敏感资源的直接访问。(保护代理)
- 日志记录:记录方法调用信息,便于调试和监控。
- 远程服务调用:简化客户端与远程服务之间的交互。(远程代理)
- 当需要用一个消耗资源较少的对象来代表一个消耗资源较多的对象,从而降低系统开销、缩短运行时间时可以使用虚拟代理。
- 当需要为一个对象的访问(引用)提供一些额外的操作时可以使用智能引用代理
四、代码案例(Java)
假设我们需要设计一个简单的图片加载系统,其中包含一个Image
接口和它的具体实现RealImage
。我们将使用代理模式来优化图片的加载过程。
1. 定义接口
public interface Image {
void display();
}
2. 实现真实主题类
public class RealImage implements Image {
private String fileName;
public RealImage(String fileName) {
this.fileName = fileName;
loadFromDisk(fileName);
}
@Override
public void display() {
System.out.println("Displaying " + fileName);
}
private void loadFromDisk(String fileName) {
System.out.println("Loading " + fileName);
}
}
3. 创建代理类
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 Demo {
public static void main(String[] args) {
Image image = new ProxyImage("test_10mb.jpg");
// 图片将不会立即加载
image.display(); // 只有在这一步才会真正加载图片并显示
}
}
典型代码(c++)
抽象主题类典型代码
abstract class Subject
{
public abstract void Request();
}
真实主题类典型代码
class RealSubject : Subject
{
public override void Request()
{
//业务方法具体实现代码
}
}
代理类典型代码
class Proxy : Subject
{
private RealSubject realSubject = new RealSubject(); //维持一个对真实主题对象的引用
public void PreRequest()
{
…...
}
public override void Request()
{
PreRequest();
realSubject.Request(); //调用真实主题对象的方法
PostRequest();
}
public void PostRequest()
{
……
}
}
其他案例
- 某软件公司承接了某信息咨询公司的收费商务信息查询系统的开发任务,该系统的基本需求如下:
(1) 在进行商务信息查询之前用户需要通过身份验证,只有合法用户才能够使用该查询系统;
(2) 在进行商务信息查询时系统需要记录查询日志,以便根据查询次数收取查询费用。
该软件公司开发人员已完成了商务信息查询模块的开发任务,现希望能够以一种松耦合的方式向原有系统增加身份验证和日志记录功能,客户端代码可以无区别地对待原始的商务信息查询模块和增加新功能之后的商务信息查询模块,而且可能在将来还要在该信息查询模块中增加一些新的功能。
现使用代理模式设计并实现该收费商务信息查询系统。
- 论坛权限控制代理
在一个论坛中已注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。
在本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限。
- 数学运算代理
模拟应用远程代理来访问另外一个应用程序域中的对象,如果在远程实现了加减乘除等运算,在本地需要调用,那么可以考虑在本地设置一个代理
虚拟代理
对于一些占用系统资源较多或者加载时间较长的对象,可以给这些对象提供一个虚拟代理
在真实对象创建成功之前虚拟代理扮演真实对象的替身,而当真实对象创建之后,虚拟代理将用户的请求转发给真实对象
使用一个“虚假”的代理对象来代表真实对象,通过代理对象来间接引用真实对象,可以在一定程度上提高系统的性能
应用:
由于对象本身的复杂性或者网络等原因导致一个对象需要较长的加载时间,此时可以用一个加载时间相对较短的代理对象来代表真实对象(结合多线程技术)
一个对象的加载十分耗费系统资源,让那些占用大量内存或处理起来非常复杂的对象推迟到使用它们的时候才创建,而在此之前用一个相对来说占用资源较少的代理对象来代表真实对象,再通过代理对象来引用真实对象(用时间换取空间)
缓冲代理
为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果
可以避免某些方法的重复执行,优化系统性能
应用:
public static class ProductDataProxy
{
private static readonly int productTimeout = int.Parse(ConfigurationManager.AppSettings ["ProductCacheDuration"]);
private static readonly bool enableCaching = bool.Parse(ConfigurationManager. AppSettings["EnableCaching"]);
public static IList GetProductsByCategory(string category)
{
Product product = new Product();
//如果缓存被禁用,则直接通过product对象来获取数据
if (!enableCaching)
{
return product.GetProductsByCategory(category);
}
string key = "product_by_category_" + category;
//从缓存中获取数据
IList data = (IList )HttpRuntime.Cache[key];
//如果缓存中没有数据则执行如下代码
if (data == null)
{
data = product.GetProductsByCategory(category);
//通过工厂创建AggregateCacheDependency对象
AggregateCacheDependency cd = DependencyFacade.GetProductDependency ();
//将数据存储在缓存中,并添加必要的AggregateCacheDependency对象
HttpRuntime.Cache.Add(key, data, cd, DateTime.Now.AddHours(product Timeout), Cache.NoSlidingExpiration, CacheItemPriority.High, null);
}
return data;
}
……
}
五、优缺点分析
优点 | 描述 |
---|---|
✅ 增强安全性 | 可以在代理中加入权限检查等逻辑。 |
✅ 灵活性增加 | 无需修改原始类即可添加新功能。客户端可以针对抽象主题角色进行编程,增加和更换代理类无须修改源代码,符合开闭原则,系统具有较好的灵活性和可扩展性。 |
✅ 延迟加载/懒加载 | 提高了应用启动速度和运行效率。 |
其他 | 能够协调调用者和被调用者,在一定程度上降低了系统的耦合度 |
其他类型优点分析:
远程代理:可以将一些消耗资源较多的对象和操作移至性能更好的计算机上,提高了系统的整体运行效率
虚拟代理:通过一个消耗资源较少的对象来代表一个消耗资源较多的对象,可以在一定程度上节省系统的运行开销
缓冲代理:为某一个操作的结果提供临时的缓存存储空间,以便在后续使用中能够共享这些结果,优化系统性能,缩短执行时间
保护代理:可以控制对一个对象的访问权限,为不同用户提供不同级别的使用权限
缺点 | 描述 |
---|---|
❌ 复杂性增加 | 需要维护额外的代理类,增加了系统的复杂度。(例如远程代理) |
❌ 调试难度加大 | 调试时可能会因为多了一层间接性而变得困难。 |
性能问题 | 由于在客户端和真实主题之间增加了代理对象,因此有些类型的代理模式可能会造成请求的处理速度变慢(例如保护代理) |
六、最终小结
代理模式是一个强大的工具,能够帮助我们在不影响原有系统的情况下,扩展功能或优化性能。它特别适用于那些希望对外隐藏细节、进行访问控制或者实现延迟加载的应用场景。
理解并掌握代理模式可以帮助你更好地管理复杂的系统架构,尤其是在处理远程服务调用、资源管理和安全控制等方面。
📌 一句话总结:
代理模式就像是给某个对象穿上一层“外套”,让外界只能看到这件“外套”,而无法直接接触内里的实体,以此达到保护、优化或控制的目的。
部分内容由AI大模型生成,注意识别!