如何继承依赖注入服务的类?通用仓储继承优化问询
解决通用仓储继承导致的子类依赖耦合问题
关于当前继承方式的可接受性
如果你的项目规模不大、通用仓储的依赖长期稳定,这种继承方式其实是可接受的,算不上DI服务继承的“不可避免缺陷”——它只是在开发效率和SOLID原则之间做了取舍,毕竟你通过继承省去了大量转发代码的编写。但缺点也很明显:子类和父类的具体实现强耦合,一旦父类新增/修改依赖,所有子类的构造函数都要跟着改,违反了依赖倒置原则。
简易解决方案
方案1:组合模式 + 默认接口方法(推荐,完全遵循SOLID)
利用C# 8+的默认接口方法,让专属仓储只需要实现自定义逻辑,基础CRUD通过接口默认实现转发给通用仓储实例,既不用写重复的转发代码,又避免了继承带来的耦合。
示例代码:
// 基础仓储接口定义所有CRUD方法 public interface IRepository<T> { Task<T?> GetByIdAsync(int id); Task AddAsync(T entity); Task UpdateAsync(T entity); Task DeleteAsync(int id); Task<List<T>> GetAllAsync(); } // 通用仓储实现基础CRUD public class GeneralRepository<T> : IRepository<T> { private readonly IConfiguration _config; public GeneralRepository(IConfiguration config) => _config = config; // 实现所有CRUD方法 public async Task<T?> GetByIdAsync(int id) { /* 通用实现 */ } public async Task AddAsync(T entity) { /* 通用实现 */ } // ... 其他CRUD方法 } // 专属仓储接口继承基础接口,添加自定义方法 public interface IUserRepository : IRepository<User> { Task<User?> GetByEmailAsync(string email); } // 专属仓储类组合通用仓储实例,只实现自定义方法 public class UserRepository : IUserRepository { private readonly IRepository<User> _generalRepo; // 只注入自己需要的额外依赖,无需关心通用仓储的依赖 public UserRepository(IRepository<User> generalRepo) => _generalRepo = generalRepo; // 自定义业务方法 public async Task<User?> GetByEmailAsync(string email) { /* 自定义查询逻辑 */ } // 用默认接口方法自动转发CRUD,不用手动写重复代码 public Task<User?> GetByIdAsync(int id) => _generalRepo.GetByIdAsync(id); public Task AddAsync(User entity) => _generalRepo.AddAsync(entity); public Task UpdateAsync(User entity) => _generalRepo.UpdateAsync(entity); public Task DeleteAsync(int id) => _generalRepo.DeleteAsync(id); public Task<List<User>> GetAllAsync() => _generalRepo.GetAllAsync(); }
注册DI时只需分别注册通用仓储和专属仓储:
services.AddScoped(typeof(IRepository<>), typeof(GeneralRepository<>)); services.AddScoped<IUserRepository, UserRepository>();
方案2:封装父类依赖(兼容现有继承结构)
如果不想重构现有继承体系,可以把通用仓储的所有依赖封装成一个单独的依赖类,减少子类对父类具体依赖的感知:
// 封装通用仓储的所有依赖 public class GeneralRepositoryDependencies { public IConfiguration Config { get; } public DbContext DbContext { get; } // 假设有其他依赖 public GeneralRepositoryDependencies(IConfiguration config, DbContext dbContext) { Config = config; DbContext = dbContext; } } // 通用仓储依赖这个封装类 public class GeneralRepository<T> { protected readonly GeneralRepositoryDependencies _deps; public GeneralRepository(GeneralRepositoryDependencies deps) => _deps = deps; // 使用_deps.Config、_deps.DbContext实现CRUD } // 子类只需传递这个封装类,不用关心内部具体依赖 public class UserRepository : GeneralRepository<User> { public UserRepository(GeneralRepositoryDependencies deps, IUserService userService) : base(deps) { // 处理自己的额外依赖 } }
这种方式下,即使通用仓储后续新增依赖,只需修改GeneralRepositoryDependencies,所有子类的构造函数都不用改动,大幅降低耦合。
方案3:让DI容器自动解析父类依赖(最小改动)
如果不想做任何结构调整,至少可以让DI容器自动帮你注入父类的依赖,不用手动在业务代码里维护依赖传递——只要你在DI容器中正确注册了父类所需的服务(比如IConfiguration),子类构造函数只需声明父类的依赖,容器会自动传递给base():
public class UserRepository : GeneralRepository<User> { // 只需声明父类需要的IConfiguration,容器会自动注入并传递给base public UserRepository(IConfiguration config, IUserService userService) : base(config) { // ... } }
这种方式没有解决本质耦合,但至少减少了手动维护依赖的工作量。
内容的提问来源于stack exchange,提问作者kj49
相关产品推荐
相关产品推荐

