代理模式
大约 11 分钟约 3186 字
代理模式
简介
代理(Proxy)为另一个对象提供替代品或占位符,以控制对原对象的访问。理解代理的多种变体(虚拟代理、保护代理、远程代理、智能引用),有助于在不修改原对象的前提下添加控制逻辑。
代理模式的核心思想是"控制访问" —— 当你不想让客户端直接访问某个对象,而是希望通过一个中间层来添加额外的控制逻辑时,就使用代理模式。这个中间层就是"代理",它和真实对象实现相同的接口,客户端无法区分自己是在和代理还是真实对象交互。
在日常生活中,代理无处不在:律师是客户的法律代理、经纪人是投资者的交易代理、VPN 是网络访问的代理。在软件中,代理模式同样广泛:延迟加载、权限控制、缓存、日志、远程调用等都通过代理实现。
特点
结构分析
UML 类图
+--------------------+
| ISubject | <-- 接口
+--------------------+
| +Request() |
+--------------------+
^
|
+---------+---------+
| |
+-------------+ +-------------+
| RealSubject | | Proxy |
| (真实对象) | | (代理) |
+-------------+ +-------------+
| +Request() | | -real: Real |
+-------------+ | +Request() |
+-------------+代理变体对比
虚拟代理 保护代理 智能引用代理
+--------+ +--------+ +--------+
| 延迟 | | 权限 | | 缓存 |
| 加载 | | 检查 | | 日志 |
+--------+ +--------+ +--------+
| 图片 | | 文档 | | 天气 |
| 大对象 | | 访问 | | 服务 |
+--------+ +--------+ +--------+实现
虚拟代理(延迟加载)
// 真实对象 — 图片加载耗时
public interface IImage
{
void Display();
string GetName();
}
public class RealImage : IImage
{
private readonly string _filename;
public RealImage(string filename)
{
_filename = filename;
LoadFromDisk(); // 模拟耗时加载
}
private void LoadFromDisk() => Console.WriteLine($"加载图片: {_filename}(耗时操作)");
public void Display() => Console.WriteLine($"显示图片: {_filename}");
public string GetName() => _filename;
}
// 虚拟代理 — 延迟到真正需要时才加载
public class ImageProxy : IImage
{
private readonly string _filename;
private RealImage? _realImage;
public ImageProxy(string filename) => _filename = filename;
public void Display()
{
_realImage ??= new RealImage(_filename); // 懒加载
_realImage.Display();
}
public string GetName() => _filename;
}
// 使用
IImage image = new ImageProxy("large_photo.jpg"); // 不加载
Console.WriteLine("图片代理已创建,但未加载");
image.Display(); // 此时才真正加载
image.Display(); // 第二次不重新加载保护代理(权限控制)
public interface IDocument
{
string Title { get; }
string Content { get; set; }
void Read();
void Edit(string newContent);
void Delete();
}
public class RealDocument : IDocument
{
public string Title { get; }
public string Content { get; set; }
public RealDocument(string title, string content)
{
Title = title;
Content = content;
}
public void Read() => Console.WriteLine($"读取: {Title}");
public void Edit(string newContent) { Content = newContent; Console.WriteLine($"编辑: {Title}"); }
public void Delete() => Console.WriteLine($"删除: {Title}");
}
public enum UserRole { Guest, User, Editor, Admin }
public class DocumentProxy : IDocument
{
private readonly RealDocument _document;
private readonly UserRole _role;
public DocumentProxy(RealDocument document, UserRole role)
{
_document = document;
_role = role;
}
public string Title => _document.Title;
public string Content
{
get => _role >= UserRole.User ? _document.Content : throw new UnauthorizedAccessException("无权读取");
set => _role >= UserRole.Editor ? _document.Content = value : throw new UnauthorizedAccessException("无权编辑");
}
public void Read()
{
if (_role < UserRole.User) throw new UnauthorizedAccessException("无读取权限");
_document.Read();
}
public void Edit(string newContent)
{
if (_role < UserRole.Editor) throw new UnauthorizedAccessException("无编辑权限");
_document.Edit(newContent);
}
public void Delete()
{
if (_role < UserRole.Admin) throw new UnauthorizedAccessException("无删除权限");
_document.Delete();
}
}
// 使用
var doc = new RealDocument("机密报告", "敏感内容");
IDocument guestDoc = new DocumentProxy(doc, UserRole.Guest);
IDocument userDoc = new DocumentProxy(doc, UserRole.User);
IDocument adminDoc = new DocumentProxy(doc, UserRole.Admin);
guestDoc.Read(); // 异常: 无读取权限
userDoc.Read(); // OK
userDoc.Edit("x"); // 异常: 无编辑权限
adminDoc.Delete(); // OK智能引用代理(缓存 + 日志)
public interface IWeatherService
{
Task<WeatherInfo> GetWeatherAsync(string city);
}
public record WeatherInfo(string City, double Temperature, string Description, DateTime FetchedAt);
public class WeatherServiceProxy : IWeatherService
{
private readonly IWeatherService _realService;
private readonly ConcurrentDictionary<string, (WeatherInfo Info, DateTime CachedAt)> _cache = new();
private readonly TimeSpan _cacheDuration = TimeSpan.FromMinutes(10);
private readonly ILogger _logger;
public WeatherServiceProxy(IWeatherService realService, ILogger logger)
{
_realService = realService;
_logger = logger;
}
public async Task<WeatherInfo> GetWeatherAsync(string city)
{
// 缓存检查
if (_cache.TryGetValue(city, out var cached) && DateTime.UtcNow - cached.CachedAt < _cacheDuration)
{
_logger.LogInformation("缓存命中: {City}", city);
return cached.Info with { FetchedAt = cached.CachedAt };
}
// 日志
_logger.LogInformation("请求天气: {City}", city);
var sw = Stopwatch.StartNew();
try
{
var result = await _realService.GetWeatherAsync(city);
_cache[city] = (result, DateTime.UtcNow);
_logger.LogInformation("天气获取成功: {City}, 耗时 {Ms}ms", city, sw.ElapsedMilliseconds);
return result;
}
catch (Exception ex)
{
_logger.LogError(ex, "天气获取失败: {City}", city);
// 降级: 返回过期缓存
if (_cache.TryGetValue(city, out var expired))
{
_logger.LogWarning("使用过期缓存: {City}", city);
return expired.Info;
}
throw;
}
}
}实战:泛型延迟加载代理
public class LazyProxy<T> where T : class
{
private readonly Func<T> _factory;
private T? _instance;
private bool _created;
public LazyProxy(Func<T> factory) => _factory = factory;
public T Value => _instance ??= _factory();
public bool IsCreated => _created;
public void Reset()
{
if (_instance is IDisposable disposable) disposable.Dispose();
_instance = null;
_created = false;
}
}
// 使用
var heavyResource = new LazyProxy<ExpensiveResource>(() =>
{
Console.WriteLine("创建昂贵的资源...");
return new ExpensiveResource();
});
// 此时不创建
Console.WriteLine($"资源已创建: {heavyResource.IsCreated}"); // false
// 首次访问时创建
var resource = heavyResource.Value;
Console.WriteLine($"资源已创建: {heavyResource.IsCreated}"); // true动态代理实现
使用 DispatchProxy
// C# 内置的动态代理 — 无需手动编写代理类
public class LoggingProxy<T> : DispatchProxy
where T : class
{
private T _decorated;
public static T Create(T decorated)
{
var proxy = Create<T, LoggingProxy<T>>() as LoggingProxy<T>;
proxy!._decorated = decorated;
return (T)(object)proxy!;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
Console.WriteLine($"[调用] {targetMethod!.Name}");
var sw = Stopwatch.StartNew();
try
{
var result = targetMethod.Invoke(_decorated, args);
Console.WriteLine($"[完成] {targetMethod.Name}, 耗时: {sw.ElapsedMilliseconds}ms");
return result;
}
catch (Exception ex)
{
Console.WriteLine($"[异常] {targetMethod.Name}: {ex.Message}");
throw;
}
}
}
// 使用 — 自动为任何接口添加日志
public interface IOrderService
{
Task<string> CreateOrder(int productId, int quantity);
decimal CalculateTotal(int orderId);
}
public class OrderService : IOrderService
{
public Task<string> CreateOrder(int productId, int quantity)
{
Console.WriteLine($"创建订单: 商品{productId} x {quantity}");
return Task.FromResult("ORD-001");
}
public decimal CalculateTotal(int orderId)
{
Console.WriteLine($"计算订单总价: {orderId}");
return 299.99m;
}
}
// 创建代理 — 自动添加日志
IOrderService service = LoggingProxy<IOrderService>.Create(new OrderService());
service.CreateOrder(1, 5); // 自动输出调用日志
service.CalculateTotal(1); // 自动输出调用日志重试代理
public class RetryProxy<T> : DispatchProxy
where T : class
{
private T _decorated;
private int _maxRetries;
private TimeSpan _delay;
public static T Create(T decorated, int maxRetries = 3, TimeSpan? delay = null)
{
var proxy = Create<T, RetryProxy<T>>() as RetryProxy<T>;
proxy!._decorated = decorated;
proxy._maxRetries = maxRetries;
proxy._delay = delay ?? TimeSpan.FromSeconds(1);
return (T)(object)proxy!;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
for (int i = 0; i <= _maxRetries; i++)
{
try
{
return targetMethod!.Invoke(_decorated, args);
}
catch (Exception ex) when (i < _maxRetries)
{
Console.WriteLine($"[重试] {targetMethod!.Name} 第{i + 1}次失败: {ex.Message}");
Thread.Sleep(_delay);
}
}
// 最后一次重试失败则抛出异常
return targetMethod!.Invoke(_decorated, args);
}
}
// 使用 — 为外部服务调用添加自动重试
IOrderService retryService = RetryProxy<IOrderService>.Create(
new OrderService(), maxRetries: 3, delay: TimeSpan.FromSeconds(2));限流代理
public class RateLimitingProxy<T> : DispatchProxy
where T : class
{
private T _decorated;
private int _maxCalls;
private TimeSpan _window;
private Queue<DateTime> _callTimes = new();
public static T Create(T decorated, int maxCalls, TimeSpan window)
{
var proxy = Create<T, RateLimitingProxy<T>>() as RateLimitingProxy<T>;
proxy!._decorated = decorated;
proxy._maxCalls = maxCalls;
proxy._window = window;
return (T)(object)proxy!;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
var now = DateTime.UtcNow;
// 清理过期记录
while (_callTimes.Count > 0 && now - _callTimes.Peek() > _window)
_callTimes.Dequeue();
if (_callTimes.Count >= _maxCalls)
{
throw new InvalidOperationException(
$"调用频率超限: {_maxCalls}次/{_window.TotalSeconds}秒");
}
_callTimes.Enqueue(now);
return targetMethod!.Invoke(_decorated, args);
}
}
// 使用 — 限制每分钟最多调用 60 次
IOrderService limitedService = RateLimitingProxy<IOrderService>.Create(
new OrderService(), maxCalls: 60, window: TimeSpan.FromMinutes(1));代理链 — 组合多个代理
// 代理链 — 多层代理叠加
public static class ProxyChain
{
public static T Create<T>(T target, params Func<T, T>[] proxies) where T : class
{
T current = target;
foreach (var proxyFactory in proxies)
{
current = proxyFactory(current);
}
return current;
}
}
// 使用 — 同时叠加日志、重试、缓存
IOrderService service = ProxyChain.Create<IOrderService>(
new OrderService(),
target => RetryProxy<IOrderService>.Create(target, maxRetries: 3),
target => LoggingProxy<IOrderService>.Create(target),
target => CachingProxy<IOrderService>.Create(target, TimeSpan.FromMinutes(5))
);缓存代理
public class CachingProxy<T> : DispatchProxy where T : class
{
private T _decorated;
private TimeSpan _expiration;
private Dictionary<string, (object? Result, DateTime CachedAt)> _cache = new();
public static T Create(T decorated, TimeSpan expiration)
{
var proxy = Create<T, CachingProxy<T>>() as CachingProxy<T>;
proxy!._decorated = decorated;
proxy._expiration = expiration;
return (T)(object)proxy!;
}
protected override object? Invoke(MethodInfo? targetMethod, object?[]? args)
{
// 只缓存无参或值类型参数的方法
var cacheKey = $"{targetMethod!.Name}_{string.Join("_", args ?? Array.Empty<object?>())}";
if (_cache.TryGetValue(cacheKey, out var cached)
&& DateTime.UtcNow - cached.CachedAt < _expiration)
{
Console.WriteLine($"[缓存命中] {targetMethod.Name}");
return cached.Result;
}
var result = targetMethod.Invoke(_decorated, args);
_cache[cacheKey] = (result, DateTime.UtcNow);
return result;
}
}代理模式的性能考量
// 性能对比 — 不同代理方式的调用开销
public class ProxyPerformanceTest
{
// 直接调用: ~5ns
// 虚拟代理: ~10ns
// DispatchProxy: ~50-100ns
// 真实代理类: ~10ns
// Castle DynamicProxy: ~100-200ns
public void Benchmark()
{
var service = new OrderService();
var iterations = 1000000;
// 直接调用
var sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
service.CalculateTotal(i);
Console.WriteLine($"直接调用: {sw.ElapsedMilliseconds}ms");
// 手动代理
var manualProxy = new ManualOrderProxy(service);
sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
manualProxy.CalculateTotal(i);
Console.WriteLine($"手动代理: {sw.ElapsedMilliseconds}ms");
// DispatchProxy
var dynamicProxy = LoggingProxy<IOrderService>.Create(service);
sw = Stopwatch.StartNew();
for (int i = 0; i < iterations; i++)
dynamicProxy.CalculateTotal(i);
Console.WriteLine($"动态代理: {sw.ElapsedMilliseconds}ms");
}
}
public class ManualOrderProxy : IOrderService
{
private readonly IOrderService _inner;
public ManualOrderProxy(IOrderService inner) => _inner = inner;
public Task<string> CreateOrder(int productId, int quantity) => _inner.CreateOrder(productId, quantity);
public decimal CalculateTotal(int orderId) => _inner.CalculateTotal(orderId);
}代理模式 vs 装饰器模式 vs 适配器模式
代理模式 装饰器模式 适配器模式
+--------+ +--------+ +--------+
| 控制 | | 增强 | | 转换 |
| 访问 | | 功能 | | 接口 |
+--------+ +--------+ +--------+
| 目的: | | 目的: | | 目的: |
| 限制 | | 增强 | | 兼容 |
+--------+ +--------+ +--------+
| 代理 | | 透明 | | 接口 |
| 决定 | | 委托 | | 不同 |
+--------+ +--------+ +--------+最佳实践
- 代理接口与真实对象一致:代理和真实对象应实现相同的接口,保证客户端无感知。
- 按需选择代理类型:根据需求选择虚拟代理(延迟加载)、保护代理(权限控制)或智能引用代理(缓存/日志)。
- 避免过度代理:每个代理都增加一层间接调用,只在有明确需求时使用。
- 使用 DispatchProxy 实现动态代理:C# 的
DispatchProxy类可以动态创建代理,无需手动编写代理类。 - 注意线程安全:虚拟代理的懒加载在多线程环境下需要同步。
优点
缺点
总结
代理模式在不改变接口的前提下控制对象访问。虚拟代理实现延迟加载(如图片懒加载),保护代理实现权限控制,智能引用代理添加缓存和日志。C# 中 DispatchProxy 类可实现类似动态代理。建议在需要控制访问、添加横切关注点(缓存、日志、权限)时使用代理模式,也可与装饰器模式配合使用。
代理模式的本质价值在于:当你需要在客户端和目标对象之间插入一层控制逻辑时,代理模式提供了一种透明的方式来实现。客户端代码不需要任何修改,代理在幕后默默地完成延迟加载、权限检查、缓存等额外工作。
关键知识点
- 模式不是目标,降低耦合和控制变化才是目标。
- 先找变化点、稳定点和协作边界,再决定是否引入模式。
- 同一个模式在不同规模下的收益和代价差异很大。
项目落地视角
- 优先画出参与对象、依赖方向和调用链,再落到代码。
- 把模式放到一个真实场景里,比如支付、规则引擎、工作流或插件扩展。
- 配合单元测试或契约测试,保证重构后的行为没有漂移。
常见误区
- 为了看起来"高级"而套模式。
- 把简单问题拆成过多抽象层,导致阅读和排障都变难。
- 只会背 UML,不会解释为什么这里需要这个模式。
进阶路线
- 继续关注模式之间的组合用法,而不是孤立记忆。
- 从业务建模、演进策略和团队协作角度看模式的适用性。
- 把模式结论沉淀为项目模板、基类或约束文档。
适用场景
- 当你准备把《代理模式》真正落到项目里时,最适合先在一个独立模块或最小样例里验证关键路径。
- 适合在业务规则频繁变化、分支增多或对象协作复杂时引入。
- 当你希望提高扩展性,但又不想把系统拆得过度抽象时,这类主题很有参考价值。
落地建议
- 先识别变化点,再决定是否引入模式,而不是反过来套模板。
- 优先为模式的边界、依赖和调用路径画出简单结构图。
- 把模式落到一个明确场景,例如支付、规则计算、插件扩展或工作流。
排错清单
- 检查抽象层是否过多,导致调用路径和责任不清晰。
- 确认引入模式后是否真的减少了条件分支和重复代码。
- 警惕"为了模式而模式",尤其是在简单业务里。
复盘问题
- 如果把《代理模式》放进你的当前项目,最先要验证的输入、输出和失败路径分别是什么?
- 《代理模式》最容易在什么规模、什么边界条件下暴露问题?你会用什么指标或日志去确认?
- 相比默认实现或替代方案,采用《代理模式》最大的收益和代价分别是什么?
