曾几何时,某些设计模式是 .NET 优秀架构的基石。我们为 DbContext 包装仓储层,为缓存构建装饰器,精心设计线程安全的单例类。这些模式确实解决过问题——至少在当年如此。

但 .NET 已颠覆游戏规则。随着高级依赖注入、源生成器、Minimal API 和 C#12 新特性的到来,许多经典模式悄然过时。并非它们有错,而是 .NET 已替你处理了这些关切。

1. 仓储层 + 工作单元

传统模式

public interface IRepository  // 冗余抽象Task GetByIdAsync(int id);void Add(T entity);void Remove(T entity);public interface IUnitOfWork  // 重复造轮子Task CommitAsync();}

过时原因
EF Core 已通过 DbSet 暴露仓储逻辑,并通过 SaveChangesAsync 管理工作单元。自定义包装器增加无意义抽象层。

现代方案

public class ProductService(MyContext _db)  // 直接注入DbContextpublic Task GetByIdAsync(int id) =>_db.Products.FindAsync(id).AsTask();public async Task AddProduct(Product product!)_db.Products.Add(product);await _db.SaveChangesAsync();  // 原生工作单元}

结合 C#12 必需成员与空检查:

public record Productpublic required string Name { get; init; }  // 编译时安全}
2. 服务定位器

反例

var logger = ServiceLocator.Get();  // 隐藏依赖

过时原因
静态访问使依赖不可见,测试如同噩梦。

现代方案

// Minimal API 显式注入app.MapGet("/log", (ILogger logger) =>logger.LogInformation("优雅的日志记录"));// 构造函数注入public class AuthService(ILogger _logger)public void Authenticate() =>_logger.LogInformation("用户认证中...");}
3. 日志包装器

传统写法

public class LoggerHelper  // 手工包装器private readonly ILogger _logger;public void LogFailedLogin(string userId) =>_logger.LogWarning($"登录失败: {userId}");  // 字符串拼接低效}

现代方案

public static partial class AuthLog  // 源生成器[LoggerMessage(EventId = 101, Level = LogLevel.Warning,Message = "用户 {UserId} 登录失败")]public static partial void FailedLogin(ILogger logger, string userId);// 调用:零分配开销AuthLog.FailedLogin(_logger, userId);
4. 工厂方法模式

过时实现

public class WidgetFactory : IWidgetFactory  // 简单场景的过度设计public Widget Create() => new Widget();}

现代替代

// 依赖注入容器自动构造builder.Services.AddTransient();// 需额外参数时var widget = ActivatorUtilities.CreateInstance(provider, arg1, arg2);
5. 横切关注点装饰器

传统做法

public class CachingRepository : IRepository  // 业务逻辑污染private readonly IRepository _inner;public async Task Get(int id)// 缓存逻辑与业务耦合return await _inner.Get(id);}

现代方案

// 中间件统一处理app.UseResponseCaching();app.MapGet("/products", async (MyContext db) =>await db.Products.ToListAsync()).CacheResponse(60);  // 声明式缓存
6. 适配器模式

过时实现

public class LegacyAdapter : INewApi  // 冗余包装类private readonly LegacyService _legacy;public Data Get() => Convert(_legacy.Fetch());}

现代替代

public static class LegacyExtensions  // 扩展方法直接适配public static Data ToNewData(this LegacyService svc) =>new Data(svc.Fetch().Value);// 调用:legacyService.ToNewData()
7. 手动单例模式

传统写法

public sealed class Logger  // 线程安全手工实现private static readonly Lazy _instance = new(...);public static Logger Instance => _instance.Value;}

现代方案

// 容器托管生命周期builder.Services.AddSingleton();  // 自动处理线程安全
8. 手工 DTO 映射

过时做法

public static OrderDto Map(Order order) =>  // 手工赋值new OrderDto { Id = order.Id, Total = order.Total };

现代方案

// C#记录类型简化public record OrderDto(int Id, decimal Total);// 一行完成映射var dto = new OrderDto(order.Id, order.Total);
9. 共享引用与深层命名空间

传统风格

namespace MyApp.Features.Submodule.Core;  // 冗长嵌套

现代优化

// GlobalUsings.csglobal using System.Text.Json;global using Microsoft.Extensions.Logging;// 文件顶部简化namespace MyApp.Features;  // 文件作用域命名空间
过时模式与现代替代对照表

过时模式✅ .NET 10 替代方案仓储层+工作单元直接使用 DbContext服务定位器构造函数/Minimal API 注入日志包装器源生成器 [LoggerMessage]工厂模式DI 容器 / ActivatorUtilities装饰器中间件/端点过滤器适配器扩展方法手动单例AddSingleton() 注册手工 DTO 映射C# 记录类型深层命名空间文件作用域命名空间

重构行动指南

放手依赖多年的模式或许不适——但随着 .NET 进化,这些模式常成为冗余脚手架。

三步重构法
1️⃣从小处开始

  • • 将单个仓储替换为直接 DbContext 调用
  • • 用源生成日志替换一个包装器

2️⃣转移横切逻辑

  • • 将装饰器功能迁移到中间件层

3️⃣体验收益

  • • 代码可读性提升 40%+
  • • 变更速度提高 2 倍
  • • 调试时间减少 60%