如何在企业项目中使用依赖项注入
本文关键字:依赖 注入 企业 项目 | 更新日期: 2023-09-27 18:37:01
想象一下,您有一个应用程序,其中有数百个类实现了数十个"高级"接口(即组件级(。有没有一种推荐的方法来做依赖性注射(比如用团结(。是否应该有一个可用于自举的"通用容器",可以作为Singleton访问?是否应该传递一个容器,所有实例都可以在哪里注册实例?所有的事情都应该由RegisterType在启动时的某个地方完成吗?如何在需要时使容器可访问。构造函数注入似乎是错误的,有争议的是它是标准的方式,你必须将接口从组件级别传递到它在启动时使用的最底层,或者引用被搁置,最终以"知道你住在哪里"的反模式结束。而且:拥有一个"可用"的容器可能会让开发人员产生在客户端环境中解析服务器组件的想法。如何避免这种情况?
欢迎任何讨论!
编辑以澄清
我想出了一个有点真实的例子来更好地了解我看到的问题。
让我们想象一下这个应用程序是一个hifi系统。该系统具有cd播放器(集成在cd机架中(和用于播放音乐的usb端口(集成在usb机架中(。
现在,cd播放器和usb端口应该可以播放mp3音乐了。
我在附近有一个mp3解码器,它是可注入的。
现在我启动hifi系统。还没有插入cd并且没有插入U盘。我现在不需要mp3解码器了。
但是对于构造函数注入,我必须已经注入cd机架和usb机架的依赖关系。
如果我从不插入mp3 cd或mp3 usb棒怎么办?
我应该在光盘架上放一个参考资料吗?所以当一张mp3光盘是插入的,我手头有一个花色器?(我觉得不对(
cd机架的子系统中需要解码器,该子系统只有在插入mp3时才启动。现在我没有集装箱了在cd机架中,这里的构造函数注入如何?
首先,依赖注入是一种不需要容器的设计模式。DI模式状态:
依赖注入是一种软件设计模式,它允许在运行时而不是编译时选择组件
以Guice(java依赖注入框架(为例,在java中,Guice是一个DI框架,但它本身不是一个容器。
.Net中的大多数DI工具实际上都是容器,因此需要填充容器才能注入依赖项
我不喜欢每次都必须在容器中注册每个组件的想法,我只是讨厌这样。有几个工具可以帮助您根据约定自动注册组件,我不使用Unity,但我可以为您指出例如Ninject或AutoFac
实际上,我正在编写一个小型实用程序,根据惯例使用几乎任何DI工具自动注册组件,它仍处于开发阶段
关于您的问题:
是否应该有一个可用于自举的"通用容器",可以作为Singleton访问?
答案是肯定的,(有一种工具可以抽象所使用的DI工具,它被称为ServiceLocator(这就是DI工具的工作方式,有一个静态容器可用于应用程序,但是,不建议在域对象中使用它来创建实例,这被认为是反模式
顺便说一句,我发现这个工具在运行时注册组件非常有用:
http://bootstrapper.codeplex.com/
是否应该传递一个容器,所有实例都可以在哪里注册实例?
没有。那将违反德米特的法律。如果您决定使用容器,最好在应用程序启动时注册组件
如何在需要时使容器可接近
使用公共服务定位器,你可以在应用程序中的任何地方使用它,但正如我所说,不建议在域对象中使用它来创建所需的实例,而是在对象的构造函数中注入对象,并让DI工具自动注入正确的实例。
现在基于此:
构造函数注入似乎是错误的,有争议的是它是标准的方式,你必须将接口从组件级别传递到它在启动时使用的最底层,或者引用被搁置,最终以"知道你住在哪里"的反模式结束
这让我觉得你没有为你的应用程序大量编写单元测试,这很糟糕。因此,我的建议是,在选择要使用的DI工具之前,或者在考虑这个问题的所有答案之前,请参考以下链接,这些链接专注于一件事:编写干净的可测试代码,这是迄今为止你能回答自己问题的最佳来源
清洁代码会谈:
-
http://www.youtube.com/watch?v=wEhu57pih5w&feature=播放器嵌入式
-
http://www.youtube.com/watch?v=RlfLCWKxHJ0&feature=播放器嵌入式
文章
-
http://misko.hevery.com/2010/05/26/do-it-yourself-dependency-injection/
-
http://misko.hevery.com/code-reviewers-guide/
-
PDF中的上一个链接http://misko.hevery.com/attachments/Guide-Writing%20Testable%20Code.pdf
以下链接是强烈推荐的
http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/
http://www.loosecouplings.com/2011/01/how-to-write-testable-code-overview.html
首先,有CompositionRoot模式,您可以尽快设置依赖项,在桌面中设置Main((,在web中设置global.asax/app_start。然后,依赖构造函数注入,因为这会使您的依赖关系变得清晰。
然而,您仍然需要一些东西来实际解决依赖关系。我知道至少有两种方法。
第一个是使用服务定位器(这几乎等于使容器成为一个单例(。对于很多人来说,这被认为是一种反模式,但它确实非常有效。每当你需要服务时,你都会向你的集装箱索取:
... business code...
var service = ServiceLocator.Current.GetInstance<IMyService>();
service.Foo();
ServiceLocator只使用您在CompositionRoot中设置的一个容器。
另一种方法是依赖于作为singleton可用的对象工厂,并将容器注入其中:
var service = IMyServiceFactory.Instance.CreateService();
诀窍是工厂的实现在内部使用容器来解析服务。然而,这种方法,加上一个额外的工厂层,使您的业务代码独立并且不知道IoC!您可以自由地完全重新设计工厂,以不在内部使用IoC,并且仍然维护每一行业务代码。
换句话说,将容器/定位器隐藏在工厂层后面只是一个技巧。
虽然我很想使用前一种方法(直接依赖定位器(,但我更喜欢后者。只是感觉更干净。
我想知道这里是否还有其他可行的选择。
集装箱是否应绕过
不,因为这会导致服务定位器反模式。
其中所有实例都可以注册实例
服务应该在应用程序的启动路径中注册,而不是按类型本身注册。当你有多个应用程序(如web应用程序、web服务、WPF客户端(时,通常会有一个通用的引导程序项目,它将所有服务连接在一起,用于共享层(但每个应用程序仍然有其唯一的连接,因为没有一个应用程序的行为相同(。
所有的事情都应该由RegisterType在启动的某个地方完成吗
是的,你应该在启动时把所有东西都连接起来。
如何在需要时使容器可访问。
你不应该。应用程序应该忽略容器的使用(如果使用了任何容器,因为这是可选的(。如果你不这样做,你会让很多事情变得更加困难,比如测试。但是,您可以在应用程序的启动路径中定义的类型(也称为CompositionRoot(中注入容器。这样,应用程序就不会知道容器的任何信息。
构造函数注入似乎是错误的,有争议的标准方式
构造函数注入是首选的注入依赖项的方式。然而,重构现有的应用程序以注入构造函数可能会带来挑战。在**罕见的情况下,构造函数注入不起作用,您可以恢复到属性注入,或者当无法构建完整的对象图时,您可以注入工厂。当工厂实现是组合根的一部分时,可以让它依赖于容器。
我发现一个非常有用的模式是命令/处理程序模式,它可以建立在依赖注入模式和SOLID设计原则之上。我发现这在较小的应用程序中是一种有用的模式,但当应用程序变得很大时,比如企业应用程序,它就会大放异彩。
现在我启动hifi系统。还没有插入cd,也没有usb棒插好了。我现在不需要mp3解码器了。
这似乎非常适合Setter注入(=C#中的属性注入(。使用NeutralDecoder或NullDecoder作为默认值,并在需要时注入Mp3Decoder。您可以手动完成,也可以使用DI容器和条件/后期绑定。
http://blog.springsource.com/2007/07/11/setter-injection-versus-constructor-injection-and-the-use-of-required/
我们通常建议人们使用构造函数注入强制协作器和所有其他属性的setter注入。