C#整洁架构:非服务类抽象层构建的最佳实践问询
迁移至.NET 6整洁架构的实践建议
拆分混乱的助手类
- 按职责切分模块:把助手类里的代码按业务场景、功能属性拆分,比如将缓存操作、步骤执行、缓存失效逻辑分别抽成独立的类,彻底告别“大杂烩”式的代码结构。
- 严格遵循单一职责:每个新类只聚焦一件事,比如原来助手类中负责缓存失效的代码,单独封装为
CacheInvalidationHandler,仅处理缓存失效相关逻辑;步骤执行逻辑抽为StepExecutionService,只负责业务步骤的流转与执行。 - 移除硬编码实例化:把所有直接
new的对象调用,全部替换为构造函数注入,先统一交由DI容器管理,后续再根据需求调整抽象层设计。
Manager/Invalidator/Executor/Handler类的抽象方案
优先采用「接口+构造注入」
和你处理服务类的思路一致,这是最符合整洁架构依赖倒置原则的方案:
- 在应用层定义抽象接口,比如针对
CacheInvalidator,定义ICacheInvalidator,包含InvalidateCacheAsync(string cacheKey)等核心方法; - 在基础设施层实现具体逻辑,比如
RedisCacheInvalidator(假设使用Redis作为缓存),实现接口的所有方法; - 在.NET 6的DI容器中注册依赖关系:
services.AddScoped<ICacheInvalidator, RedisCacheInvalidator>(); - 在需要使用的类的构造函数中注入
ICacheInvalidator,完全替代直接实例化的方式。
工厂模式的适用场景
如果这类类的实例化需要动态参数(比如根据不同业务类型创建不同的StepExecutor),再考虑引入工厂:
- 定义工厂接口
IStepExecutorFactory,包含CreateExecutor(StepType stepType)方法; - 工厂内部可依赖DI容器或根据参数创建对应实例,调用方仅依赖工厂接口,无需关心具体实现细节。
包装器的使用场景
如果需要对原有逻辑添加切面处理(比如日志、异常捕获),或者封装第三方库的调用,可以用包装器模式:
- 定义
LoggingCacheInvalidator实现ICacheInvalidator,内部注入真正的RedisCacheInvalidator; - 在调用
InvalidateCacheAsync前后添加日志或异常处理逻辑; - DI注册时替换为包装器:
services.AddScoped<ICacheInvalidator, LoggingCacheInvalidator>(),同时注册底层的RedisCacheInvalidator供包装器依赖。
分步实施的过渡技巧
- 小步迭代,逐步迁移:不要一次性重写整个助手类,先挑选一个独立的小功能(比如缓存失效)完成拆分、抽象、注入的全流程,测试通过后再处理下一个模块,降低重构风险。
- 保留过渡兼容层:如果旧代码暂时无法完全脱离原助手类,可以先在助手类内部通过DI获取依赖,逐步引导调用方迁移到新的类,最后再删除助手类。示例代码:
// 过渡阶段的助手类,仅作为兼容层 public class LegacyHelper { private readonly ICacheInvalidator _cacheInvalidator; public LegacyHelper(ICacheInvalidator cacheInvalidator) { _cacheInvalidator = cacheInvalidator; } public void InvalidateCache(string key) { // 复用新实现,逐步淘汰旧逻辑 _cacheInvalidator.InvalidateCacheAsync(key).Wait(); } }
- 利用.NET 6 DI特性简化迁移:比如用
AddKeyedServices处理同接口多实现的场景,或用ActivatorUtilities动态创建需要额外参数的实例(适合暂时无法完全依赖注入的过渡场景)。
核心原则参考
- 依赖倒置原则:高层业务模块不依赖底层基础设施实现,两者都依赖应用层的抽象接口;抽象不依赖细节,细节依赖抽象。
- 整洁架构边界:应用层的抽象定义不能依赖基础设施层的具体实现,确保内层(业务逻辑)不被外层(技术实现)绑定。
- 控制反转:所有对象实例的创建交由DI容器管理,类只声明依赖的抽象,不负责具体实例的创建。
内容的提问来源于stack exchange,提问作者Dumitru Laurenţiu
相关产品推荐
相关产品推荐

