如果将依赖项注入比所需高出许多“级别”

本文关键字:高出许 级别 依赖 注入 如果 | 更新日期: 2023-09-27 18:34:25

我正在使用SOLID原则编写一个C#ASP.NET MVC Web应用程序。

我写了一个ViewModelService,它依赖于AccountServiceRepositoryService,所以我在ViewModelServer中注入了这两个服务。

PermissionService取决于HttpContextBase,以便使用GetOwinContext()获取UserManager的实例。控制器有一个需要使用HttpContextBase实例 - 所以似乎我必须将HttpContextBase实例注入ViewModelService然后将其注入PermissionService

因此,就代码而言,我有:

public ViewModelService
public CategoryRepository(ApplicationDbContext context, IPermissionService permissionservice)
public AccountService(HttpContextBase httpcontext, IPrincipal securityprincipal)

为了实例化ViewModelService,然后我这样做:

new ViewModelService(
    new CategoryRepository(
            new ApplicationDbContext(), 
            new PermissionService(
                new AccountService(HttpContext, Thread.CurrentPrincipal),
                new UserPasswordRepository(new ApplicationDbContext()),
                new ApplicationSettingsService())),
    new PasswordRepository(
            new ApplicationDbContext(), 
            new PermissionService(
                new AccountService(HttpContext, Thread.CurrentPrincipal), 
                new UserPasswordRepository(new ApplicationDbContext()),
                new ApplicationSettingsService())),
    new ModelValidatorService());

应该从那么多"级别"向上注入依赖关系,还是有更好的方法?

如果将依赖项注入比所需高出许多“级别”

需要

取得平衡。

一方面,你的思想流派坚持认为所有依赖关系必须由类公开才能"正确"注入。 (这是一种认为服务定位器之类的东西是反模式的思想流派。 这是有好处的,但极端地说,你会发现自己现在的位置。 在一些复合模型中,恰到好处的复杂性,本身具有复合模型,导致聚合根需要注入大量依赖项来满足更深层次模型的依赖项。

我个人发现这在这样的情况下会产生耦合。 这就是 DI 旨在解决的问题,而不是创建的内容。

另一方面,您有允许服务定位器方法的思想流派,其中模型可以在内部调用一些公共域服务来解决它的依赖关系。 这样做是有好处的,但极端地说,你会发现你的依赖项鲜为人知,如果无法解决任何给定的依赖项,则可能会出现运行时错误。 (基本上,您可能会在更高级别收到错误,因为使用对象从来不知道使用的对象需要未提供的内容。

就我个人而言,我经常使用服务定位器方法(主要是因为它是一种非常方便的模式,可以将 DI 引入遗留域,作为更大的重构练习的一部分,这是我专业做的很多事情(,并且从未遇到过此类问题。

无论哪种方式,都有阴阳。 我认为每个解决方案空间都有自己的平衡。 如果您发现直接注入使系统难以维护,则可能值得调查服务位置。 相反,如果整个域模型本身本质上是耦合的,并且此 DI 问题只是该耦合的症状,而不是它的原因,也可能值得调查。

是的,依赖注入的全部意图是预先编写大型对象图。从组合根编写对象图,合成根是应用程序中具有组合对象图的单一职责的位置。这不是任何特定的控制器,而是一个单独的类,它由控制器及其依赖项组成。

组合根必须有权访问它需要编写的所有类型,除非你想进入后期绑定策略(除非有特定需求,否则我通常建议不要这样做(。

我坚信服务定位器比依赖注入更糟糕。它们可以是一种有用的遗留技术,也是通往更好事物的有用垫脚石,但如果您正在设计新的东西,那么请避开。

造成这种情况的主要原因是服务定位器会导致代码具有隐式依赖项,这使得代码不太清晰并破坏封装。它还可能导致运行时错误,而不是编译时错误和交互测试。

您的示例使用构造函数注入,这通常是最合适的依赖关系注入形式:

public ViewModelService(ICategoryRepository categoryRepository, IPasswordRepository passwordRepository, IModelValidatorService modelValidator) { ... }

这有明确的依赖关系,这很好。这意味着如果不传入其依赖项,就无法创建对象,并且如果您尝试这样做,将得到编译时错误而不是运行时错误。它也有利于封装,因为只要查看类的接口,你就知道它需要什么依赖项。

您可以使用服务定位器执行此操作,如下所示:

public ViewModelService() 
{ 
    var categoryRepository = CategoryRepositoryServiceLocator.Instance;
    var passwordRepository = PasswordRepositoryServiceLocator.Instance;   
    var modelValidator  FModelValidatorServiceLocator.Instance;
    ...
}

这具有隐式依赖关系,您不能仅通过查看接口来判断,还必须查看实现(这会破坏封装(。您也可以忘记设置其中一个服务定位器,这将导致运行时异常。

在你的例子中,我认为你的视图模型服务很好。它引用抽象(ICategoryRepository等(,并不关心这些抽象是如何创建的。您用于创建ViewModelService的代码有点丑陋,我建议使用控制反转容器(例如Castle Windsor,StructureMap等(来提供帮助。

在温莎城堡中,您可以执行以下操作:

container.Register(Classes.FromAssemblyNamed("Repositories").Pick().WithServiceAllInterfaces());
container.Register(Component.For<IAccountService>().ImplementedBy<AccountService>()); 
container.Register(Component.For<IApplicationDBContext>().ImplementedBy<IApplicationDBContext>()); 
container.Register(Component.For<IApplicationSettingsService>().ImplementedBy<IApplicationSettingsService>()); 
var viewModelService = _container.Resolve<ViewModelService>();

在开始之前,请务必阅读并理解"注册、解析、释放"和"组合根"模式。

祝你好运!