将你的应用程序容器包含在服务构造函数中是否被认为是不良的 DI 实践?

3

首先,我正在使用Simple Injector,但我认为这个问题适用于任何DI框架。

在创建要注册到应用程序的服务时,通过构造函数将服务容器传递到服务中是否被认为是不良实践?

例如。考虑以下代码:

//IServiceInterface.cs
interface IServiceInterface {}

//MyService.cs
//All standard using statements here...
using SimpleInjector;

class MyService : IServiceInterface
{
    private _container {get; set;}

    public MyService(Container container) 
    {
        _container = container;

        //Construct!
    }
}

//MyApp.cs

public Contrainer container;
....

//My application bootstrapper method
void Bootstrap()
{
    var container = new Container();
    container.RegisterSingle<IServiceInterface >(() => new MyService(container));
    container.Verify();
    this.container = container;
}

如您从上面的方法中看到的,我定义了一个服务类,它需要接收一个Simple Injector容器。当我注册容器时,我将与我的应用相关联的容器传递给该服务。

这似乎是在定义将在单独项目中使用、甚至可能位于不同命名空间中的服务时很有用的方法,该服务将在应用程序生命周期的某个时刻需要注册新服务。然而,在我尝试这样做之前,我没有在任何示例中看到过这样做,因此我想要确保这种方法是正确的。

这种行为是否被认为是良好的 DI 实践?如果不是,那么如何获取应用程序的 DI 容器,并在服务内部需要注册新服务时进行注册?

更新--其他细节

我决定为一个新项目使用依赖注入,原因有二。其一,这是一个庞大的项目,可能包括 10-12 个 Visual Studio 项目;其二,这些项目中的许多代码都是多年来从一个应用程序复制和粘贴到另一个应用程序中,然后稍加修改而得到的。够了,是时候编写我们自己的业务逻辑框架,以使其符合我们公司的需求。

这个第一个大项目似乎是开始 DI 和我自己的框架的地方。为了逐层构建和测试该应用程序,我正在定义许多接口和“外壳”服务类。这样,我就可以只连接我的顶级应用程序��并在其所需的项目完成并链接到我的解决方案时更新依赖项。

由于这是如此庞大的应用程序,我有些服务需要依赖于其他服务...进而依赖于更多的服务。

我的想法是,我的应用程序只应注册用来验证用户和加载视图所需的服务。视图服务应注册模型视图。模型视图服务应注册它们关联的模型服务,后者将注册数据库连接服务... 呼哧,最终将注册一个服务器端同步服务,该服务将注册本地 DB 连接服务和 Web 应用程序服务。噢!听起来混乱吗?好吧,有点混乱。

我的想法是,我可以定义这些类,它们可以接受一个容器对象,然后每个服务将使用容器来获取可能已经存在的任何底层服务,如果还没有创建,则实例化一个新服务。

例如,我的用户认证服务可能通过一个ILocalDB服务缓存信息,应与我的模型服务共享。如果在启动应用程序时注册所有这些服务,应用程序将变得缓慢,而整个注册过程看起来会相当混乱。

我认为这必须有一个优雅的解决方案。我错过了什么?


你最后一句话听起来完全是反过来的。容器应该注册服务,而服务本身使用容器来检索服务。不是相反。 - McGarnagle
不,我想你理解了。我猜我的传递容器的方法似乎比有4-5级构造函数更有意义。(例如,服务被注入到服务中,再被注入到服务中...)特别是如果我需要在需要时延迟加载其中一些服务。 - RLH
1
但是构造函数注入的复杂性和需要延迟加载都是可以接受的 - 这些都是 DI 容器旨在解决的问题。只要您已经向容器注册了每个依赖项类型,那么您就不必手动调用其中一个复杂的构造函数。DI 容器具有管理对象范围的工具:http://simpleinjector.codeplex.com/wikipage?title=ObjectLifestyleManagement&referringTitle=Home - McGarnagle
1
注入容器是服务定位器模式的一种变体。在Stackoverflow上已经有很多关于它的文章了。但这不是一个好的模式。请阅读这篇文章:http://blog.ploeh.dk/2010/02/03/ServiceLocatorisanAnti-Pattern/ - Steven
@Steven:我已经阅读了这篇文章,我理解了Mark Seemann的观点。如果你能在回答中提供一个1-2句的摘要,并且对于如何解决依赖关系深入到依赖其他依赖项的项目中,给出一个合理的解释,那么你就得到了Mark的答案。 - RLH
显示剩余4条评论
2个回答

3

我认为传递容器不是一个好的设计。你本质上是传递了一个通用的对象构造工厂,这样就绕过了明确表达你的依赖关系所固有的价值。实践中,你可以在你的服务类上声明一个静态属性container来完成同样的操作。

如果我需要动态构建对象,我通常会传递一个工厂而不是显式的实例。比如,传递ILogFactory而不仅仅是ILog的实例。这样从代码中可以明显看出哪些东西是动态构建的,代价则是一些工厂和构造函数参数。

另一个选择是,如果您不需要创建多个依赖项的实例,但您知道只需要其中的一些,那么请确保对象的构建是轻量级的。这样,即使您明确声明了所有的依赖项,因为它们只在你真正使用它们时才产生成本,所以也无妨。


我对这一切非常陌生。我看到了“需要”DI,但这是一个如此庞大的主题。我将尝试从这个角度进行更多的研究,并回答任何问题或想法。谢谢。 - RLH

2
标题中的基本问题已经被@Steven回答了 - 是的,将容器注入到任何类中通常被认为是不好的实践。@Steven是SimpleInjector的作者,并坚定地坚持这个原则 - 在这里看
我不清楚你还在问什么,但这里有一些信息可能会有所帮助。
  • SimpleInjector通常不建议在应用程序的整个生命周期中注册服务,而更喜欢在组合根中启动时完成所有注册。在这里看

当从容器中解析第一个类型时,容器将被锁定以防止进一步更改。在此之后调用任何注册方法将导致容器抛出异常。容器无法被解锁。这种行为是固定的,不能更改。如果必须在此点之后注册新类型,则可以使用未注册类型解决方案。

  • 如果您有需要一些时间来实例化的对象,那么您可以注入 Lazy<> 实例,这样一个实例在调用代码中显式引用之前/除非它被显式引用,否则不会被实例化。请参见此处

  • 对象生命周期管理应该处理传递现有实例/实例化新实例的所有复杂性。请参见此处

  • 为了使注册过程不变得“复杂”,您可以将该过程分成每个注册解决方案区域的类,例如 DAL、CommandHandlers、Services 等。这些类应该是内部的组合根,负责引导整个应用程序,整个引导过程只在启动时调用一次。组合根甚至不需要显式引用包含您解决方案中所有实现的所有程序集;它只需要引用您定义的所有服务即可。请参见此处 和此处


1
我对这些材料的理解存在一些空白,这就是为什么你可能没有完全理解我的问题的原因——我可能没有正确地提出我的问题。尽管如此,我认为你已经提供了足够有用的信息来指引我朝着正确的方向前进。谢谢。 - RLH
@RLH 我完全理解 - 9个月前我还没有听说过 DI。 - qujck
1
非常好的回答,无需补充。 - Steven

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接