IoC,容器应该放在哪里?

27

我正在使用Castle Windsor完成我的项目。我开始注意到需要在代码中的不同位置调用IoC容器来创建新对象。这种对容器的依赖会使我的代码难以维护。

有两个解决方案可以解决这个问题

我尝试创建抽象工厂作为容器的包装,并将其注入到需要创建对象的应用程序部分中。这个方法可行,但存在一些缺点,因为Castle很难将自己的容器作为依赖项注入。所以我必须手动做,这几乎是违背IoC容器的初衷。

我已经使用主应用控制器类来封装IoC容器并作为中央工厂/存储库。这很成功,但这个类变得太大了,就像一个中央神对象,几乎每个其他对象都引用它。

这两种解决方案都有一定的效果,但都有缺点。因此,我很好奇其他人是否遇到过相同的问题并找到了更好的解决方案。


编辑 问题不是针对依赖于对象B的对象A。在这里,我通常只使用构造函数注入,并且一切正常。有时候,类型为A的对象需要在其生命周期内创建变量数量的其他类型为B的对象。我不确定如何做到这一点。

@Blair Conrad:到目前为止,维护问题并不严重。我有一些类依赖于调用container.Resolve<>的容器对象。我不希望我的代码依赖于我认为是基础设施的东西。我仍在尝试各种方法,所以当我从ninject切换到castle时,我注意到必须更改很多代码。

@flowers: 嗯。我喜欢你的第一个解决方案。它结合了我尝试过的两种解决方案中有效的部分。我认为我还是太注重对象而不是接口/职责。我尝试过定制工厂,但我想让它们在幕后使用容器来创建对象,而我还没有找到如何以清晰的方式将容器注入对象。


我很好奇,这些答案可能会帮助我们回答问题。你们遇到了哪些维护问题? - Blair Conrad
7个回答

12
请不要使用静态类,例如IoC.Container.Resolve或ContainerFactory.GetContainer!这会使代码更加复杂,难以测试、维护、重用和阅读。通常,任何单个组件或服务只有一个注入点——构造函数(可选属性)。一般来说,您的组件或服务类不应该知道容器的存在。如果您的组件确实需要内部动态解析(即根据名称解析异常处理策略或工作流程),则建议考虑通过高度专业的提供程序借用IoC功能

谢谢提供链接。当我有时间时,我需要仔细阅读它,但我认为我尝试了类似的东西,然后决定不使用它,而是使用抽象工厂来创建动态对象,而不是使用IoC容器。我只使用容器来创建工厂本身。 - Mendelt
2
@Rinat,使用原始场景:您在A中,需要n个B实例。不参考容器,您如何获取您的Bs? - flipdoubt
互联网档案馆链接:http://web.archive.org/web/20080921233716/http://rabdullin.com/how-to-avoid-tight-ioc-coupling-in-non-deterministic-resolution-scenarios/ - cyang

11

这是一篇非常优秀的文章,帮助我澄清了这个问题! - Greg B
喜欢这个链接。但我更希望得到一个高层次的摘要,而不仅仅是一个链接。 - KFL
1
不再可用 -> 这是最后一个有效的快照 http://web.archive.org/web/20100209011441/http://blogs.msdn.com/nblumhardt/archive/2008/12/27/container-managed-application-design-prelude-where-does-the-container-belong.aspx - aksu

4
在我的应用程序中,依赖注入的主要好处至少是能够编写上下文无关的代码。从这个角度来看,你的第二种解决方案似乎真的削弱了DI可能给你带来的好处。如果“上帝对象”为每个引用它的类公开不同的接口,那么它可能不是太邪恶。但是,如果你走得那么远,我不明白为什么你不把它全部送到篮筐里。
例如:您的上帝对象有一个getFoo()方法和一个getBar()方法。对象A需要一个Foo,对象B需要一个Bar。如果A只需要一个Foo,则应该将Foo直接注入A,并且A根本不应该知道上帝。但是,如果A需要不断创建Foos,则避免传递God所造成的损害几乎是不可避免的。但是,您可以通过缩小对神的引用类型来保护自己所受的损害。如果使上帝实现FooFactory并为A提供由上帝实现的FooFactory的引用,您仍然可以以一种上下文中立的方式编写A中的代码。这将改进代码重用的机会,并增加您对更改上帝不会导致意外副作用的信心。例如,您可以确定在从God中删除getBar()时,类A不会出错。
但是……如果您还要拥有所有这些接口,那么最好编写专门的工厂类,并在容器内将所有对象,包括工厂,连接在一起,而无需包装容器。容器仍然可以配置工厂。

2
尽管我欣赏“专门建造的工厂”的明确性,甚至自己也使用它们,但这在我的设计中感觉像是一种代码异味,因为公共接口(小写“i”)会随着新的工厂和/或每个实现的新Getx方法而不断变化。阅读杰里米·米勒(Jeremy Miller)的文章《It's time for IoC Container Detente》后,我怀疑泛型和注入容器本身是正确的方式。
我会将Ninject、StructureMap或Windsor包装在某种IServiceLocator接口中,就像Jeremy文章中提出的那样。然后有一个容器工厂,在您的任何代码中都简单返回一个IServiceLocator,甚至在循环中,正如您最初建议的那样。
IServiceLocator container = ContainerFactory.GetContainer(); 
while( keepLooping )
{
    IExample example = container.GetInstance<IExample>();
    keepLooping = example.DoWork();
}

你的容器工厂始终可以返回相同的实例,你可以交换IoC框架,无论如何。

1
作为对@flipdoubt的跟进,
如果你最终决定使用服务定位器类型的模式,你可能会想要查看http://www.codeplex.com/CommonServiceLocator。它提供了一些与几个流行的IoC框架(如windsor、structuremap)相关的绑定,可能会有所帮助。
祝好运。

谢谢!我会去看看。我发现创建自己的服务定位器非常容易,但我可能会从他们的做法中得到一些灵感! - Mendelt

1

在这种情况下,我建议使用您提到的强类型工厂进行注入。这些工厂可以包装容器,但可以允许传递其他上下文并进行额外处理。例如,OrderFactory上的Create可以接受上下文参数。

在通用服务定位器上具有静态依赖关系是一个坏主意,因为您会失去意图和上下文。当IoC构建实例时,它可以根据一系列因素(如配置文件、上下文等)提供正确的依赖项,因为它具有全局视角。

CommonServiceLocator并不是为此目的而设计的,尽管人们可能会想要使用它。CommonServiceLocator的主要目的是为了应用程序/框架,以便跨IoC容器兼容。然而,使用它的应用程序应该只调用定位器一次,以最优方式构建组件及其依赖关系的层次结构。它不应该直接再次调用。如果我们有某种方式来强制执行,那就好了。在Prism(http://www.microsoft.com/compositewpf)中,我们引入了一个IContainerFacade来构建模块。虽然这是一个服务定位器,但它是一个低级别的服务定位器。回顾过去,我们可能应该创建一个ModuleFactory或类似的东西,并使用IContianerFacade来获取它,然后使用它来解决模块与直接访问Facade相比的问题。事后诸葛亮。不过,它足够低级,不会对事情产生实质性影响。

关于CSL,我们在命名上曾经纠结过,因为它可能会导致混淆。最终,我们决定使用CSL,因为从技术上讲,该接口并没有为您提供DI。


0

这是一个非常普遍的问题。Windsor内置的Typed Factory Facility将为您提供使用工厂的好处,而不会出现上述缺点。


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