结构型设计模式之代理模式
1. 代理模式概述
代理模式(Proxy Pattern)是结构型设计模式的一种,它为其他对象提供一种代理以控制对这个对象的访问。代理对象在客户端和目标对象之间起到中介的作用,可以在不改变目标对象的情况下,透明地添加额外的功能或控制逻辑。
在现实生活中,代理模式随处可见。比如,我们通过房产中介购买房子,中介作为房主的代理;我们请律师代表我们处理法律事务,律师作为我们的代理。在软件开发中,代理模式同样有着广泛的应用。
2. 代理模式的结构
代理模式主要包含以下几个角色:
- Subject(抽象主题): 定义了RealSubject和Proxy的共同接口,这样就可以在任何使用RealSubject的地方使用Proxy。
- RealSubject(真实主题): 定义了Proxy所代表的真实对象,包含了最终要执行的业务逻辑。
- Proxy(代理): 保存一个引用使得代理可以访问实体,并提供一个与Subject接口相同的接口,这样代理就可以用来替代实体。
3. 代理模式的类型
3.1 静态代理
静态代理是在编译时就确定了代理与被代理对象之间的关系。代理类和被代理类都实现同一个接口或继承同一个类,在代理类中维护一个被代理对象的引用,通过构造函数注入或者其他方式初始化。
3.2 动态代理
动态代理是在运行时动态生成代理类,不需要手动编写代理类的代码。在C#中,可以通过反射、表达式树、IL生成等技术实现动态代理。
3.3 远程代理
远程代理为一个位于不同地址空间的对象提供本地代表。例如,Web服务客户端代理就是一种远程代理,它使开发人员可以像调用本地方法一样调用远程服务。
3.4 虚拟代理
虚拟代理根据需要创建开销很大的对象,使得对象只在需要时才会被真正创建。例如,图片查看器中的图片加载就可以使用虚拟代理实现,当需要显示图片时才真正加载图片数据。
3.5 保护代理
保护代理用于控制对原始对象的访问,用于对象应该有不同的访问权限的情况。例如,在员工信息系统中,不同级别的用户对员工信息的访问权限不同。
3.6 缓存代理
缓存代理为开销大的运算结果提供临时存储,以便多个客户端共享使用。例如,Web浏览器的缓存就是一种缓存代理,它可以缓存网页内容,减少网络请求。
4. C#中的代理实现
在C#中,代理模式的实现主要有以下几种方式:
4.1 基于接口的静态代理
这是最基本的代理实现方式,代理类和被代理类都实现同一个接口。
// 定义抽象主题接口
public interface ISubject
{
void Request();
}
// 实现真实主题类
public class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("RealSubject: 处理请求");
}
}
// 实现代理类
public class Proxy : ISubject
{
private RealSubject _realSubject;
public Proxy()
{
// 可以在构造函数中创建被代理对象
_realSubject = new RealSubject();
}
public void Request()
{
// 在调用真实对象的方法前可以执行一些预处理
Console.WriteLine("Proxy: 请求前的处理");
// 调用真实对象的方法
_realSubject.Request();
// 在调用真实对象的方法后可以执行一些后处理
Console.WriteLine("Proxy: 请求后的处理");
}
}
// 客户端代码
public class Client
{
public static void Main()
{
// 使用代理对象
ISubject subject = new Proxy();
subject.Request();
}
}
4.2 基于透明代理的动态代理
在.NET中,可以使用System.Runtime.Remoting.Proxies.RealProxy
类和System.Runtime.Remoting.Messaging.IMessage
接口实现动态代理。
using System;
using System.Reflection;
using System.Runtime.Remoting.Messaging;
using System.Runtime.Remoting.Proxies;
// 定义抽象主题接口(必须继承MarshalByRefObject才能被透明代理代理)
public interface ISubject
{
void Request();
}
// 实现真实主题类
public class RealSubject : MarshalByRefObject, ISubject
{
public void Request()
{
Console.WriteLine("RealSubject: 处理请求");
}
}
// 实现动态代理类
public class DynamicProxy : RealProxy
{
private readonly object _target;
public DynamicProxy(Type type, object target) : base(type)
{
_target = target;
}
// 重写Invoke方法来拦截方法调用
public override IMessage Invoke(IMessage msg)
{
// 转换为方法调用消息
IMethodCallMessage methodCall = msg as IMethodCallMessage;
Console.WriteLine($"DynamicProxy: 拦截方法 {methodCall.MethodName} 调用");
try
{
// 在调用真实对象的方法前执行一些预处理
Console.WriteLine("DynamicProxy: 请求前的处理");
// 调用真实对象的方法
object returnValue = methodCall.MethodBase.Invoke(_target, methodCall.InArgs);
// 在调用真实对象的方法后执行一些后处理
Console.WriteLine("DynamicProxy: 请求后的处理");
// 返回方法调用结果
return new ReturnMessage(returnValue, null, 0,
methodCall.LogicalCallContext, methodCall);
}
catch (Exception e)
{
// 处理异常
return new ReturnMessage(e, methodCall);
}
}
}
// 客户端代码
public class Client
{
public static void Main()
{
// 创建实际对象
RealSubject realSubject = new RealSubject();
// 创建动态代理
DynamicProxy proxy = new DynamicProxy(typeof(ISubject), realSubject);
// 获取代理对象(强制转换为ISubject接口)
ISubject subject = (ISubject)proxy.GetTransparentProxy();
// 通过代理对象调用方法
subject.Request();
}
}
4.3 基于DispatchProxy的动态代理(.NET Core)
在.NET Core中,可以使用System.Reflection.DispatchProxy
类实现动态代理,这种方式比RealProxy
更简洁。
using System;
using System.Reflection;
// 定义抽象主题接口
public interface ISubject
{
void Request();
}
// 实现真实主题类
public class RealSubject : ISubject
{
public void Request()
{
Console.WriteLine("RealSubject: 处理请求");
}
}
// 实现动态代理类
public class SubjectProxy : DispatchProxy
{
private ISubject _realSubject;
// 设置被代理的实际对象
public void SetRealSubject(ISubject realSubject)
{
_realSubject = realSubject;
}
// 创建代理
public static ISubject Create<T>(T target) where T : ISubject
{
// 创建代理实例
var proxy = Create<ISubject, SubjectProxy>();
// 设置被代理对象
((SubjectProxy)proxy).SetRealSubject(target);
return proxy;
}
// 实现拦截方法
protected override object Invoke(MethodInfo targetMethod, object[] args)
{
try
{
// 在调用真实对象的方法前执行一些预处理
Console.WriteLine($"SubjectProxy: 拦截方法 {targetMethod.Name} 调用");
Console.WriteLine("SubjectProxy: 请求前的处理");
// 调用真实对象的方法
var result = targetMethod.Invoke(_realSubject, args);
// 在调用真实对象的方法后执行一些后处理
Console.WriteLine("SubjectProxy: 请求后的处理");
return result;
}
catch (Exception ex)
{
// 处理异常
Console.WriteLine($"SubjectProxy: 调用过程中出现异常: {ex.Message}");
throw;
}
}
}
// 客户端代码
public class Client
{
public static void Main()
{
// 创建实际对象
RealSubject realSubject = new RealSubject();
// 创建代理
ISubject subject = SubjectProxy.Create(realSubject);
// 通过代理调用方法
subject.Request();
}
}
5. 代理模式的实际应用场景
5.1 虚拟代理示例 - 图片延迟加载
虚拟代理可以用于延迟加载大型资源,如图片、视频等。下面是一个图片延迟加载的示例:
// 定义图片加载器接口
public interface IImageLoader
{
void DisplayImage();
}
// 实现真实图片加载器
public class RealImageLoader : IImageLoader
{
private string _fileName;
public RealImageLoader(string fileName)
{
_fileName = fileName;
LoadImageFromDisk();
}
private void LoadImageFromDisk()
{
// 模拟从磁盘加载图片的耗时操作
Console.WriteLine($"加载图片: {_fileName}");
Thread.Sleep(1000); // 模拟加载耗时
}
public void DisplayImage()
{
Console.WriteLine($"显示图片: {_fileName}");
}
}
// 实现虚拟代理图片加载器
public class ProxyImageLoader : IImageLoader
{
private RealImageLoader _realImageLoader;
private string _fileName;
public ProxyImageLoader(string fileName)
{
_fileName = fileName;
}
public void DisplayImage()
{
// 在真正需要显示图片时才创建RealImageLoader对象
if (_realImageLoader == null)
{
_realImageLoader = new RealImageLoader(_fileName);
}
// 调用真实对象的方法
_realImageLoader.DisplayImage();
}
}
// 客户端代码
public class ImageGallery
{
public static void Main()
{
// 创建多个图片代理
IImageLoader image1 = new ProxyImageLoader("image1.jpg");
IImageLoader image2 = new ProxyImageLoader("image2.jpg");
IImageLoader image3 = new ProxyImageLoader("image3.jpg");
// 只有当用户需要查看图片时,才会真正加载图片
Console.WriteLine("图库已初始化,但尚未加载任何图片");
// 用户点击查看第一张图片
Console.WriteLine("\n用户点击了第一张图片");
image1.DisplayImage();
// 用户再次点击查看第一张图片(此时图片已加载,不会再次从磁盘加载)
Console.WriteLine("\n用户再次点击了第一张图片");
image1.DisplayImage();
// 用户点击查看第二张图片
Console.WriteLine("\n用户点击了第二张图片");
image2.DisplayImage();
}
}
5.2 保护代理示例 - 访问控制
保护代理可以用于控制对敏感资源的访问。下面是一个简单的访问控制示例:
// 定义文档接口
public interface IDocument
{
void Open(string username);
void Edit(string username);
void Save(string username);
}
// 实现真实文档
public class RealDocument : IDocument
{
private string _fileName;
public RealDocument(string fileName)
{
_fileName = fileName;
Load();
}
private void Load()
{
Console.WriteLine($"加载文档: {_fileName}");
}
public void Open(string username)
{
Console.WriteLine($"打开文档: {_fileName}");
}
public void Edit(string username)
{
Console.WriteLine($"编辑文档: {_fileName}");
}
public void Save(string username)
{
Console.WriteLine($"保存文档: {_fileName}");
}
}
// 实现保护代理
public class DocumentProtectionProxy : IDocument
{
private RealDocument _document;
private string _fileName;
private Dictionary<string, string[]> _userPermissions;
public DocumentProtectionProxy(string fileName)
{
_fileName = fileName;
// 初始化用户权限配置
_userPermissions = new Dictionary<string, string[]>
{
{"admin", new[] {"open", "edit", "save"}},
{"editor", new[] {"open", "edit"}},
{"viewer", new[] {"open"}}
};
}
private bool CheckPermission(string username, string operation)
{
if (!_userPermissions.ContainsKey(username))
{
Console.WriteLine($"用户 {username} 不存在");
return false;
}
bool hasPermission = _userPermissions[username].Contains(operation);
if (!hasPermission)
{
Console.WriteLine($"用户 {username} 没有 {operation} 权限");
}
return hasPermission;
}
public void Open(string username)
{
if (CheckPermission(username, "open"))
{
if (_document == null)
{
_document = new RealDocument(_fileName);
}
_document.Open(username);
}
}
public void Edit(string username)
{
if (CheckPermission(username, "edit"))
{
if (_document == null)
{
_document = new RealDocument(_fileName);
}
_document.Edit(username);
}
}
public void Save(string username)
{
if (CheckPermission(username, "save"))
{
if (_document == null)
{
_document = new RealDocument(_fileName);
}
_document.Save(username);
}
}
}
// 客户端代码
public class DocumentManager
{
public static void Main()
{
// 创建文档代理
IDocument document = new DocumentProtectionProxy("sensitive_data.doc");
// 不同用户尝试执行不同操作
document.Open("viewer"); // 成功,查看者有open权限
document.Edit("viewer"); // 失败,查看者没有edit权限
document.Save("viewer"); // 失败,查看者没有save权限
Console.WriteLine();
document.Open("editor"); // 成功,编辑者有open权限
document.Edit("editor"); // 成功,编辑者有edit权限
document.Save("editor"); // 失败,编辑者没有save权限
Console.WriteLine();
document.Open("admin"); // 成功,管理员有open权限
document.Edit("admin"); // 成功,管理员有edit权限
document.Save("admin"); // 成功,管理员有save权限
Console.WriteLine();
document.Open("guest"); // 失败,guest用户不存在
}
}
5.3 缓存代理示例 - 计算结果缓存
缓存代理可以用于缓存耗时操作的结果,提高性能。下面是一个计算结果缓存的示例:
// 定义计算服务接口
public interface ICalculator
{
double Calculate(int number);
}
// 实现真实计算服务
public class RealCalculator : ICalculator
{
public double Calculate(int number)
{
Console.WriteLine($"执行复杂计算 f({number})");
// 模拟耗时的复杂计算
Thread.Sleep(1000);
// 返回一个简单计算结果作为示例
return Math.Pow(number, 2) + Math.Sqrt(number);
}
}
// 实现缓存代理
public class CalculatorCacheProxy : ICalculator
{
private RealCalculator _realCalculator;
private Dictionary<int, double> _cache;
public CalculatorCacheProxy()
{
_realCalculator = new RealCalculator();
_cache = new Dictionary<int, double>();
}
public double Calculate(int number)
{
// 检查结果是否已缓存
if (_cache.ContainsKey(number))
{
Console.WriteLine($"从缓存返回结果 f({number})");
return _cache[number];
}
// 调用真实对象进行计算
double result = _realCalculator.Calculate(number);
// 缓存结果
_cache[number] = result;
Console.WriteLine($"缓存结果 f({number}) = {result}");
return result;
}
}
// 客户端代码
public class CalculationClient
{
public static void Main()
{
// 创建计算器代理
ICalculator calculator = new CalculatorCacheProxy();
// 首次计算,需要执行真实计算并缓存结果
Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
// 再次计算相同的值,将直接从缓存返回
Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
// 计算不同的值,需要执行真实计算并缓存结果
Console.WriteLine($"计算结果: {calculator.Calculate(10)}");
// 再次计算相同的值,将直接从缓存返回
Console.WriteLine($"计算结果: {calculator.Calculate(10)}");
// 再次计算之前计算过的值,将直接从缓存返回
Console.WriteLine($"计算结果: {calculator.Calculate(5)}");
}
}
5.4 远程代理示例 - Web API客户端
远程代理可以用于封装远程服务的复杂调用逻辑。下面是一个简单的Web API客户端代理示例:
using System.Net.Http;
using System.Threading.Tasks;
using System.Text.Json;
// 定义用户数据模型
public class User
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
}
// 定义用户服务接口
public interface IUserService
{
Task<User> GetUserAsync(int userId);
Task<bool> UpdateUserAsync(User user);
Task<bool> DeleteUserAsync(int userId);
}
// 实现远程代理
public class UserServiceProxy : IUserService
{
private readonly HttpClient _httpClient;
private readonly string _baseUrl;
public UserServiceProxy(string baseUrl)
{
_httpClient = new HttpClient();
_baseUrl = baseUrl;
}
public async Task<User> GetUserAsync(int userId)
{
// 添加日志
Console.WriteLine($"获取用户信息: 用户ID = {userId}");
try
{
// 发送GET请求
var response = await _httpClient.GetAsync($"{_baseUrl}/users/{userId}");
// 检查响应状态
response.EnsureSuccessStatusCode();
// 解析响应内容
var content = await response.Content.ReadAsStringAsync();
var user = JsonSerializer.Deserialize<User>(content, new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
return user;
}
catch (Exception ex)
{
// 记录异常
Console.WriteLine($"获取用户时出错: {ex.Message}");
throw;
}
}
public async Task<bool> UpdateUserAsync(User user)
{
// 添加日志
Console.WriteLine($"更新用户信息: 用户ID = {user.Id}, 名称 = {user.Name}");
try
{
// 序列化用户数据
var content = new StringContent(
JsonSerializer.Serialize(user),
System.Text.Encoding.UTF8,
"application/json");
// 发送PUT请求
var response = await _httpClient.PutAsync($"{_baseUrl}/users/{user.Id}", content);
// 检查响应状态
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
// 记录异常
Console.WriteLine($"更新用户时出错: {ex.Message}");
throw;
}
}
public async Task<bool> DeleteUserAsync(int userId)
{
// 添加日志
Console.WriteLine($"删除用户: 用户ID = {userId}");
try
{
// 发送DELETE请求
var response = await _httpClient.DeleteAsync($"{_baseUrl}/users/{userId}");
// 检查响应状态
return response.IsSuccessStatusCode;
}
catch (Exception ex)
{
// 记录异常
Console.WriteLine($"删除用户时出错: {ex.Message}");
throw;
}
}
}
// 客户端代码
public class ApiClient
{
public static async Task Main()
{
// 创建用户服务代理
IUserService userService = new UserServiceProxy("https://api.example.com");
try
{
// 获取用户信息
User user = await userService.GetUserAsync(123);
Console.WriteLine($"获取到用户: {user.Name}, 邮箱: {user.Email}");
// 更新用户信息
user.Name = "新名称";
bool updateResult = await userService.UpdateUserAsync(user);
Console.WriteLine($"更新用户结果: {(updateResult ? "成功" : "失败")}");
// 删除用户
bool deleteResult = await userService.DeleteUserAsync(123);
Console.WriteLine($"删除用户结果: {(deleteResult ? "成功" : "失败")}");
}
catch (Exception ex)
{
Console.WriteLine($"操作失败: {ex.Message}");
}
}
}
6. 代理模式与AOP
代理模式是面向切面编程(AOP)的基础。AOP是一种编程范式,它允许开发人员定义横切关注点(如日志记录、性能监控、事务管理等),并将这些关注点与业务逻辑分离。代理模式可以用来实现AOP,通过在代理类中添加横切关注点的代码,对目标对象的方法进行增强。
在.NET中,可以使用各种AOP框架来简化代理的创建和使用,如Castle DynamicProxy、PostSharp等。下面是一个使用Castle DynamicProxy实现AOP的简单示例:
using Castle.DynamicProxy;
// 定义接口
public interface IUserService
{
void CreateUser(string username);
void UpdateUser(int userId, string newUsername);
void DeleteUser(int userId);
}
// 实现类
public class UserService : IUserService
{
public void CreateUser(string username)
{
Console.WriteLine($"创建用户: {username}");
}
public void UpdateUser(int userId, string newUsername)
{
Console.WriteLine($"更新用户: ID = {userId}, 新用户名 = {newUsername}");
}
public void DeleteUser(int userId)
{
Console.WriteLine($"删除用户: ID = {userId}");
}
}
// 定义拦截器
public class LoggingInterceptor : IInterceptor
{
public void Intercept(IInvocation invocation)
{
// 方法执行前的处理
Console.WriteLine($"[日志] 开始执行: {invocation.Method.Name}");
Console.WriteLine($"[日志] 参数: {string.Join(", ", invocation.Arguments)}");
try
{
// 执行原方法
invocation.Proceed();
// 方法执行后的处理
Console.WriteLine($"[日志] 执行成功: {invocation.Method.Name}");
}
catch (Exception ex)
{
// 异常处理
Console.WriteLine($"[日志] 执行异常: {invocation.Method.Name}, 错误: {ex.Message}");
throw;
}
finally
{
// 清理工作
Console.WriteLine($"[日志] 结束执行: {invocation.Method.Name}");
Console.WriteLine(new string('-', 40));
}
}
}
// 客户端代码
public class AopClient
{
public static void Main()
{
// 创建代理生成器
var proxyGenerator = new ProxyGenerator();
// 创建拦截器
var interceptor = new LoggingInterceptor();
// 创建目标对象
var target = new UserService();
// 创建代理对象
IUserService proxy = proxyGenerator.CreateInterfaceProxyWithTarget<IUserService>(target, interceptor);
// 通过代理对象调用方法
proxy.CreateUser("张三");
proxy.UpdateUser(1, "李四");
proxy.DeleteUser(2);
}
}
7. 代理模式的优缺点
7.1 优点
- 职责清晰:真实主题类只需关注核心业务逻辑,而代理类负责其他非核心功能。
- 高扩展性:在不修改目标对象的前提下,扩展目标对象的功能。
- 智能化:动态代理可以在运行时动态地决定是否代理某些方法,增加了灵活性。
- 隔离性:代理对象可以起到中介和保护目标对象的作用。
7.2 缺点
- 增加系统复杂度:引入代理会增加系统的复杂度。
- 请求处理速度可能变慢:增加了一个中间层,可能会导致请求处理变慢。
- 实现复杂:动态代理实现较为复杂,需要掌握反射等高级技术。
8. 代理模式与其他模式的比较
8.1 代理模式 vs 装饰器模式
代理模式和装饰器模式在结构上非常相似,但目的不同:
- 代理模式:主要目的是控制对对象的访问,代理类通常会有自己的逻辑。
- 装饰器模式:目的是动态地给对象添加功能,装饰类通常不会改变接口,而只是增强功能。
8.2 代理模式 vs 适配器模式
- 代理模式:客户端、代理类和目标类使用相同的接口。
- 适配器模式:用于连接使用不同接口的类,适配器将一个类的接口转换成客户端期望的另一个接口。
9. 在.NET中应用代理模式的最佳实践
- 优先使用内置机制:.NET提供了多种实现代理的机制,如
DispatchProxy
、接口默认实现等。 - 考虑使用AOP框架:对于复杂的代理需求,考虑使用成熟的AOP框架,如Castle DynamicProxy。
- 接口设计:代理模式依赖于良好的接口设计,确保接口定义清晰、职责单一。
- 避免过度使用:代理会增加系统复杂度,只在确实需要时使用。
- 合理处理异常:在代理中处理异常时,确保不会掩盖原始异常信息。
10. 总结
代理模式是一种常用的结构型设计模式,它允许我们通过一个中间对象控制对另一个对象的访问。代理模式在.NET开发中有广泛的应用,从简单的访问控制到复杂的AOP实现。理解和掌握代理模式,可以帮助我们编写更加灵活、可维护的代码。
在实际应用中,代理模式常常与其他设计模式结合使用,如单例模式、工厂模式等。选择合适的代理类型和实现方式,取决于具体的业务需求和系统架构。
学习资源
- Design Patterns: Elements of Reusable Object-Oriented Software - GoF经典著作
- C# in Depth - Jon Skeet的C#进阶书籍
- Microsoft .NET文档 - DispatchProxy
- Castle DynamicProxy文档