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 和我的自制框架开始的地方。为了一次构建和测试这个应用程序的一层,我定义了很多接口和“shell”服务类。这样,我可以连接我的顶级应用程序,并在他们的项目完成并链接到我的解决方案时更新依赖项。

由于这是一个如此大的应用程序,我有服务,这将需要依赖于服务......这将进一步依赖于服务。

我的想法是我的应用程序应该只注册它需要验证我的用户和加载视图的服务。视图服务应该注册模型视图。ModelView 服务应该注册它们关联的模型服务,这将注册数据库连接服务......叹气,它最终将注册一个服务器端同步服务,它将注册一个本地数据库连接服务和 Web 应用程序服务。呸!听起来很混乱?嗯,有点像。

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

例如,我的用户身份验证服务可能会通过ILocalDB应与我的模型服务共享的服务缓存信息。如果我在启动我的应用程序时注册所有这些服务,该应用程序将变得迟缓,并且整个注册过程看起来很粗糙。

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

4

2 回答 2

3

在我看来,传入容器并不是好的设计。您实际上是在传递一个通用的对象构造工厂,因此绕过了明确说明您的依赖关系所固有的价值。实际上,您也可以container在 Service 类上声明一个静态属性。

如果我需要动态构造对象,我通常会传入工厂而不是显式实例。比如,传入一个 ILogFactory 而不仅仅是一个 ILog 的实例。这使得从代码中可以清楚地看出什么是动态构造的,代价是一些工厂和构造函数参数。

另一种选择是,如果您不需要创建依赖项的多个实例,但您知道您只需要其中一些实例,则可以确保对象构造是轻量级的。然后,显式声明所有依赖项并不重要,因为它们只有在您实际使用它们时才会产生成本。

于 2013-07-19T17:40:34.093 回答
2

@Steven 已经回答了标题中基本问题的答案 - 是的,将容器注入任何类通常被认为是不好的做法。@Steven 是 SimpleInjector 的作者,并坚定地坚持这一原则 -见这里

我不清楚您还问什么,但这里有一些信息可能会有所帮助。

  • SimpleInjector 通常不建议在整个应用程序生命周期内注册服务,并且最好在启动时在组合根中完成所有注册。看这里

当从容器中解析出第一种类型时,容器被锁定以进行进一步的更改。之后调用其中一种注册方法时,容器将抛出异常。无法解锁容器。此行为是固定的,无法更改。如果在此之后必须可以注册新类型,则可以使用未注册的类型解析。

  • 如果您有需要一些时间来实例化的对象,那么您可以注入Lazy<>实例,以便实例不会被实例化,直到/除非它在代码的调用路径中被显式引用。看这里

  • 对象生命周期管理应该处理传入现有实例/实例化新实例的所有复杂性。看这里

  • 为了防止注册过程变得“粗糙”,您可以将过程划分为类,每个类都注册解决方案的一个区域,例如 DAL、CommandHandlers、服务等。这些类应该在组合根内部,负责引导整个应用程序和整个引导过程仅在启动时调用一次。组合根甚至不需要对包含解决方案中所有实现的所有程序集的显式引用;它只需要引用您定义的所有服务。看这里 和这里

于 2013-07-22T10:22:47.690 回答