我想要一些有关解决“间接注入”上下文中的依赖注入的建议。考虑下面的例子。
考虑我有3个接口,IAdminService、IEmailService、IBillService。每个都有一个实现类型,即 AdminService、EmailService、BillService。
这里是具有实现类型的 IAdminService 示例。
public interface IAdminService
{
public void DoSomething();
}
public class AdminService : IAdminService
{
public AdminService (IServiceA A)
{
//assign service, etc.
}
public void DoSomething(){
{
A.QuerySomething(); //assume 'A' is property that is assigned in the constructor.
}
}
最重要的是,我有一个 IServiceManagement,它利用延迟加载。见下文。
public interface IServiceManagement
{
public IAdminService AdminService{ get; }
public IEmailService IEmailService{ get; }
public IBillService BillService { get; }
}
public sealed class ServiceManagement : IServiceManagement
{
private readonly Lazy<IAdminService> _adminService;
private readonly Lazy<IEmailService> _emailService;
private readonly Lazy<IBillService> _billService;
public ServiceManagement (IServiceA ServiceA,
IServiceB ServiceB,
IserviceC serviceC,
...)
{
_adminService = new Lazy<IAdminService>(() => new AdminService(ServiceA));
_emailService = new Lazy<IEmailService>(() => new EmailService(...));
_billService= new Lazy<IBillService>(() => new BillService(...));
}
public IAdminService AdminService => _adminService.Value;
public IEmailService EmailService => _emailService.Value;
public IBillService BillService => _billService.Value;
}
服务注册为范围服务(Web API),请参阅下面的扩展方法。
public static IServiceCollection RegisterLogicServices(this IServiceCollection services)
{
services.AddScoped<IServiceManagement, ServiceManagement >();
//generic services like UnitOfWork for efcore etc.
services.AddScoped<IServiceA , ServiceA >();
services.AddScoped<IServiceB , ServiceB >();
services.AddScoped<IServiceC , ServiceC >();
return services;
}
例如,要使用 IServiceManagement,在控制器级别,我们构造函数注入它,从而允许我们完整地访问该服务的所有公开暴露的方法,就像这样
public DoSomethingController(IServiceManagement manager)
{
_manager = manager
}
public IActionResult ExecuteSomething()
{
_manager.AdminService.DoSomething();
return Ok();
}
假设此时一切正常。
我想要的是,是否可以在 IAdminService 中拥有 IServiceManagement 的范围,所以我想通过 IserviceManagement 在 IAdminService 中的 IEmailService 中执行某些操作。
我可以想到的一种方法是使用 IServiceProvider,并将provider.getRequiredService() 作为构造函数的属性传递,如下所示
public ServiceManagement (IServiceA ServiceA,
IServiceB ServiceB,
IserviceC serviceC,
IServiceProvider provider
...)
{
_adminService = new Lazy<IAdminService>(() => new AdminService(ServiceA, provider.getRequiredService<IServiceManagement>()));
_emailService = new Lazy<IEmailService>(() => new EmailService(...));
_billService= new Lazy<IBillService>(() => new BillService(...));
}
我会对所有服务执行此操作,因为可能存在 IAdminService 调用 IEmailService 中的方法,并且 IEmailService 中的相同方法可能调用 IBillService 中的方法等的情况。
我想知道的是,对于此用例,使用 IserviceProvider 是否有效,例如,
让我们采取极端的情况,假设我将 IServiceManagment 注入后台服务,这会导致任何问题吗?
如果您有其他解决方案可以解决我的担忧,请提及,我想尽可能多地学习。
谢谢。
它在依赖注入的上下文中有效吗?
不,可能不是。
您的问题缺少的是为什么您需要延迟加载。这很重要,因为一般来说注入构造函数应该很简单。拥有简单的注入构造函数可以加快对象构造速度并减少延迟加载的需要。如果没有延迟加载,就不需要
IServiceManagement
了。这简化了您的解决方案并消除了当前的问题:循环依赖。
但即使无法删除缓慢加载的依赖项,将依赖项包装在
IServiceManagement
抽象中也是正确的解决方案。通过这样做,您会泄漏实现细节:服务加载速度很慢的事实。消耗依赖项不应该意识到这一点。最重要的是,通过公开 IServiceManagement
的所有依赖项,您正在创建一个解决方案,其中隐藏了类使用的实际依赖项数量,并使测试类变得更加困难,因为您必须管理一个 IServiceManagement
也在您的测试中实现。
相反,使用代理实现是一种更优雅的解决方案。在这种情况下,您可以实现一个
IAdminService
实现,它允许延迟加载真正的 IAdminService
实现。例如:
public sealed class LazyAdminService : IAdminService
{
private readonly Lazy<IAdminService> lazy;
public LazyAdminService(Lazy<IAdminService> lazy) => this.lazy = lazy;
public void DoSomething() => this.lazy.Value.DoSomething();
}
假设您使用 MS.DI 作为 DI 容器,您可以按如下方式连接:
services.AddScoped<AdminService>();
services.AddScoped<IAdminService>(c => new LazyAdminService(
new Lazy<IAdminService>(() => c.GetRequiredService<AdminService>()));
这更优雅,因为
IAdminService
的消费者没有AdminService
需要延迟初始化的概念,并且一旦AdminService
成为延迟初始化,他们不需要任何更改。