Spiga

DecoratR优雅的装饰器模式实现库

2025-08-17 22:54:34

.Net生态有一个大名鼎鼎中介者模式实现库MediatR,今天介绍一个优雅的装饰器模式实现库DecoratR。

作为软件工程师,我们不断面临在应用程序中实现横切关注点的挑战,例如日志记录、缓存、验证、重试逻辑和安全性。传统方法通常会导致:

  • 重复的样板代码散布在你的服务中。
  • 业务逻辑与基础设施关注点之间紧密耦合。
  • 由于职责混合而导致测试困难。
  • 当需求变更时,可维护性差。

考虑这个典型的服务方法:

public async Task<Order> GetOrderAsync(int orderId)
{
	_logger.LogInformation("Getting order {OrderId}", orderId); // 正在获取订单 {OrderId}

    // 先检查缓存
    var cacheKey = $"order_{orderId}";

    if (_cache.TryGetValue(cacheKey, out Order cachedOrder))
    {
        _logger.LogInformation("Order {OrderId} found in cache", orderId); // 订单 {OrderId} 在缓存中找到
        return cachedOrder;
    }

    try
    {
        // 验证输入
        if (orderId <= 0)
          thrownew ArgumentException("Invalid order ID"); // 无效的订单 ID

        // 业务逻辑埋没在基础设施代码中
        var order = await _repository.GetOrderAsync(orderId);

        // 缓存结果
        _cache.Set(cacheKey, order, TimeSpan.FromMinutes(5));

        _logger.LogInformation("Order {OrderId} retrieved successfully", orderId); // 订单 {OrderId} 成功获取

        return order;
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "Failed to get order {OrderId}", orderId); // 获取订单 {OrderId} 失败
        throw;
    }
}

此方法违反了单一职责原则(Single Responsibility Principle),并成为维护的噩梦。如果我们需要添加指标、安全检查或速率限制怎么办?该方法会变得越来越复杂。

一、DecoratR:简洁、可组合的服务装饰

DecoratR 是一个现代的 .NET 库,它通过流畅、直观的 API 将装饰器模式(Decorator pattern)引入到 Microsoft 的依赖注入(DI)容器中。它允许你以简洁、可组合的方式用横切关注点包装你的服务。

核心特性

  • 流式 API (Fluent API) — 直观且可读的装饰器链配置。
  • 常规与键控服务 (Regular & Keyed Services) — 全面支持 .NET 8+ 的键控服务。
  • 自定义工厂 (Custom Factories) — 处理复杂的依赖注入场景。
  • 泛型装饰器 (Generic Decorators) — 支持泛型装饰器。
  • 条件装饰 (Conditional Decoration) — 根据条件应用装饰器。
  • 生命周期管理 (Lifetime Management) — 控制服务的生命周期(Singleton, Scoped, Transient)。

快速开始

  1. 通过Nuget安装依赖库

    dotnet add package DecoratR
    
  2. 将复杂的服务方法转化为职责分离的简洁形式

    public class LoggingDecorator : IOrderService
    {
        private readonly IOrderService _inner;
    
        public LoggingDecorator(IOrderService inner) => _inner = inner;
    
        public string GetOrder(string orderId)
        {
            Console.WriteLine("LoggingDecorator: 开始请求 (Starting request)");
    
            var result = _inner.GetOrder(orderId);
    
            Console.WriteLine("LoggingDecorator: 请求完成 (Request completed)"); 
    
            return $"Logged({result})";
        }
    }
    
    public class CacheDecorator : IOrderService
    {
        private readonly IOrderService _inner;
    
        public CacheDecorator(IOrderService inner) => _inner = inner;
    
        public string GetOrder(string orderId)
        {
            Console.WriteLine("CacheDecorator: 正在检查缓存 (Checking cache)"); 
    
            var result = _inner.GetOrder(orderId);
    
            Console.WriteLine("CacheDecorator: 正在存入缓存 (Storing in cache)"); 
    
            return $"Cached({result})";
        }
    }
    
    public class OrderService : IOrderService
    {
        private readonly IOrderRepository _repository;
    
        public OrderService(IOrderRepository repository) => _repository = repository;
    
        public string GetOrder(string orderId)
        {
            Console.WriteLine("OrderService: 正在从数据库获取 (Fetching from database)"); 
            return $"Order: {orderId}";
        }
    }
    
  3. 注入配置

    services.Decorate<IOrderService>()
            .With<SecurityDecorator>() // 最外层 - 最先执行
            .Then<LoggingDecorator>()  // 中间层
            .Then<CacheDecorator>()    // 内层
            .Then<OrderService>()      // 基础实现 - 最内层
            .Apply();
    
    //执行流程从外向内移动:安全 (Security) → 日志 (Logging) → 缓存 (Cache) → 业务逻辑 (Business Logic)
    
  4. 使用

    // 使用
    var provider = services.BuildServiceProvider();
    var orderService = provider.GetService<IOrderService>();
    var result = orderService.GetOrder("123");
    Console.WriteLine(result);
    
    // 输出:
    // LoggingDecorator: 开始请求 (Starting request)
    // CacheDecorator: 正在检查缓存 (Checking cache)
    // OrderService: 正在从数据库获取 (Fetching from database)
    // CacheDecorator: 正在存入缓存 (Storing in cache)
    // LoggingDecorator: 请求完成 (Request completed)
    // Logged(Cached(Order: 123))
    

二、高级特性

  • 条件装饰 (Conditional Decoration) — 基于条件应用装饰器:
var isDevelopment = Environment.GetEnvironmentVariable("ENVIRONMENT") == "Development";
var enableCaching = configuration.GetValue<bool>("Features:Caching");

services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .ThenIf<RetryDecorator>(isDevelopment) // 仅在开发环境
        .ThenIf<CacheDecorator>(enableCaching) // 基于配置
        .Then<OrderService>()
        .Apply();
  • 键控服务 (.NET 8+) — 为相同服务类型创建不同的装饰器链:
// 为不同上下文使用不同配置
services.Decorate<IOrderService>("internal") // "内部"键
        .With<LoggingDecorator>()
        .Then<OrderService>()
        .Apply();

services.Decorate<IOrderService>("external") // "外部"键
        .With<SecurityDecorator>()
        .Then<RateLimitingDecorator>()
        .Then<LoggingDecorator>()
        .Then<OrderService>()
        .Apply();

// 使用
var internalService = provider.GetRequiredKeyedService<IOrderService>("internal");
var externalService = provider.GetRequiredKeyedService<IOrderService>("external");
  • 自定义工厂方法 (Custom Factory Methods) — 对于需要自定义依赖注入的复杂场景:
services.Decorate<IOrderService>()
        .With((serviceProvider, inner) =>
                  new MetricsDecorator(
                      inner,
                      serviceProvider.GetRequiredService<IMetrics>(),
                      serviceProvider.GetRequiredService<IConfiguration>()
                                     .GetValue<string>("MetricsPrefix")))
        .Then<OrderService>()
        .Apply();
  • 泛型装饰器 (Generic Decorators)
public class GenericCacheDecorator<T> : IService<T>
{
	privatereadonly IService<T> _inner;

	public GenericCacheDecorator(IService<T> inner) => _inner = inner;

	public T Get(string key) => /* 缓存逻辑 (caching logic) */ _inner.Get(key);
}

services.Decorate<IService<User>>()
        .With<GenericCacheDecorator<User>>()
        .Then<UserService>()
        .Apply();

三、实际应用场景

电子商务平台 (E-commerce Platform)

public class ECommerceConfiguration
{
	public void ConfigureServices(IServiceCollection services, IConfiguration config)
  	{
    	var enableMetrics = config.GetValue<bool>("Features:Metrics");
    	var enableRetry = config.GetValue<bool>("Features:Retry");

        // 订单处理流水线
        services.Decorate<IOrderService>()
                .With<SecurityDecorator>() // 首先认证
                .Then<ValidationDecorator>() // 验证输入
                .ThenIf<MetricsDecorator>(enableMetrics) // 可选的指标
                .ThenIf<RetryDecorator>(enableRetry) // 可选的重试逻辑
                .Then<CacheDecorator>() // 缓存近端数据
                .Then<OrderService>() // 业务逻辑
                .AsScoped() // 设置为作用域生命周期
                .Apply();

        // 支付处理 - 不同的需求
        services.Decorate<IPaymentService>()
                .With<AuditDecorator>() // 审计所有支付
                .Then<EncryptionDecorator>() // 加密敏感数据
                .Then<RateLimitingDecorator>() // 防止滥用
                .Then<PaymentService>()
                .Apply();
    }
}

多租户 SaaS 应用程序 (Multi-tenant SaaS Application)

// 不同租户层级的不同功能集
services.Decorate<IDataService>("basic-tier") 		// "基础版"键
        .With<LoggingDecorator>()
        .Then<DataService>()
        .Apply();

services.Decorate<IDataService>("premium-tier") 	// "高级版"键
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<MetricsDecorator>()
        .Then<DataService>()
        .Apply();

services.Decorate<IDataService>("enterprise-tier") 	// "企业版"键
        .With<SecurityDecorator>()
        .Then<AuditDecorator>()
        .Then<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<MetricsDecorator>()
        .Then<DataService>()
        .Apply();

四、最佳实践

  1. 装饰器顺序至关重要:

    services.Decorate<IService>()
            .With<SecurityDecorator>() // 认证/授权最先
            .Then<RateLimitingDecorator>() // 速率限制在安全之后
            .Then<LoggingDecorator>() // 在安全检查之后记录日志
            .Then<MetricsDecorator>() // 收集指标
            .Then<CacheDecorator>() // 缓存最接近数据
            .Then<BusinessService>() // 纯粹的业务逻辑
            .Apply();
    
  2. 保持装饰器职责单一

    // 好 - 单一关注点
    public class CacheDecorator : IOrderService
    {
     // 只处理缓存逻辑
    }
    
    // 坏 - 多个关注点
    public class CacheAndLogDecorator : IOrderService
    {
     // 同时处理缓存和日志记录 - 违反 SRP
    }
    
  3. 使装饰器可测试

    [Test]
    public void LoggingDecorator_Should_LogExecution() // LoggingDecorator 应记录执行
    {
        // 准备 (Arrange)
        var mockInner = new Mock<IOrderService>();
        var mockLogger = new Mock<ILogger>();
        var decorator = new LoggingDecorator(mockInner.Object, mockLogger.Object);
    
        // 执行 (Act)
        decorator.GetOrder("123");
    
        // 断言 (Assert)
        mockLogger.Verify(x => x.LogInformation(It.IsAny<string>()), Times.AtLeastOnce); // 验证至少记录一次信息
        mockInner.Verify(x => x.GetOrder("123"), Times.Once); // 验证内部服务方法被调用一次
    }
    
  4. 明智地使用条件装饰

    // 好 - 条件在启动时仅评估一次
    services.Decorate<IService>()
            .WithIf<ExpensiveDecorator>(enableExpensiveFeature) // 启用昂贵特性时才应用
            .Then<BaseService>()
            .Apply();
    
    // 坏 - 条件在每次调用时评估
    public class ConditionalDecorator : IService
    {
     public Result Execute()
      	{
        	if (someRuntimeCondition) // 每次调用都评估!
        	{
          		// 昂贵的操作
        	}
        	return _inner.Execute();
      	}
    }
    

五、性能考量

DecoratR 专为性能而设计:

  • 对于未应用的条件装饰器,运行时开销为零。
  • 最小化内存分配 — 装饰器在 DI 容器设置期间仅创建一次。
  • 方法执行期间不使用反射(Reflection)。

六、开始迁移

之前:

// 手动注册 - 易错且冗长
services.AddScoped<IOrderService>(provider =>
{
    var baseService = new OrderService(provider.GetService<IRepository>());
    var cached = new CacheDecorator(baseService, provider.GetService<ICache>());
    var logged = new LoggingDecorator(cached, provider.GetService<ILogger>());

    return logged;
});

之后:

// 简洁、流式的 API
services.Decorate<IOrderService>()
        .With<LoggingDecorator>()
        .Then<CacheDecorator>()
        .Then<OrderService>()
        .Apply();

平台支持

DecoratR 支持多个 .NET 版本:

  • .NET 6.0 — 核心装饰功能
  • .NET 7.0 — 核心装饰功能
  • .NET 8.0 — 完整功能集,包括键控服务
  • .NET 9.0 — 最新特性和优化

键控服务仅在 .NET 8.0 及更高版本中可用,但所有其他功能在所有支持的版本中均可使用。