曾几何时,某些设计模式是 .NET 优秀架构的基石。我们为 DbContext 包装仓储层,为缓存构建装饰器,精心设计线程安全的单例类。这些模式确实解决过问题——至少在当年如此。但 .NET 已颠覆游戏规则。随着高级依赖注入、源生成器、Minimal API 和 C#12 新特性的到来,许多经典模式悄然过时。并非它们有错,而是 .NET 已替你处理了这些关切。
- 仓储层 + 工作单元
传统模式:
public interface IRepository<T> // 冗余抽象
{
Task<T> 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) // 直接注入DbContext
{
public Task<Product?> 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 Product
{
public required string Name { get; init; } // 编译时安全
}
- 服务定位器
反例:
var logger = ServiceLocator.Get<ILogger>(); // 隐藏依赖
过时原因:
静态访问使依赖不可见,测试如同噩梦。现代方案:
/ Minimal API 显式注入
app.MapGet("/log", (ILogger<MyService> logger) =>
logger.LogInformation("优雅的日志记录"));
// 构造函数注入
public class AuthService(ILogger<AuthService> _logger)
{
public void Authenticate() =>
_logger.LogInformation("用户认证中...");
}
- 日志包装器
传统写法:
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);
- 工厂方法模式
过时实现:
public class WidgetFactory : IWidgetFactory // 简单场景的过度设计
{
public Widget Create() => new Widget();
}
现代替代:
public class WidgetFactory : IWidgetFactory // 简单场景的过度设计
{
public Widget Create() => new Widget();
}
- 横切关注点装饰器
传统做法:
public class CachingRepository : IRepository<T> // 业务逻辑污染
{
private readonly IRepository<T> _inner;
public async Task<T> Get(int id)
{
// 缓存逻辑与业务耦合
return await _inner.Get(id);
}
}
现代方案:
// 中间件统一处理
app.UseResponseCaching();
app.MapGet("/products", async (MyContext db) =>
await db.Products.ToListAsync())
.CacheResponse(60); // 声明式缓存
- 适配器模式
过时实现:
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()
- 手动单列模式
传统写法:
public sealed class Logger // 线程安全手工实现
{
private static readonly Lazy<Logger> _instance = new(...);
public static Logger Instance => _instance.Value;
}
现代方案:
// 容器托管生命周期
builder.Services.AddSingleton<Logger>(); // 自动处理线程安全
- 手工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);
- 共享引用与深层命名空间
传统风格:
namespace MyApp.Features.Submodule.Core; // 冗长嵌套
现代优化:
// GlobalUsings.cs
global 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%