我已经使用依赖注入(DI)有一段时间了,在构造函数、属性或方法中进行注入。我从未觉得有必要使用反转控制(IoC)容器。然而,我读得越多,我就越感到来自社区的使用IoC容器的压力。

我使用过StructureMap、NInject、Unity和Funq等。net容器。我仍然没有看到IoC容器将如何受益/改进我的代码。

我也害怕在工作中开始使用容器,因为我的许多同事会看到他们不理解的代码。他们中的许多人可能不愿意学习新技术。

请说服我,我需要使用IoC容器。当我在工作中与其他开发人员交谈时,我将使用这些论点。


当前回答

I've found that correctly implementing Dependency Injection tends to force programmers to use a variety of other programming practices that help to improve the testability, flexibility, maintainability, and scalability of code: practices like the Single Responsibility Principle, Separations of Concerns, and coding against APIs. It feels like I'm being compelled to write more modular, bite-sized classes and methods, which makes the code easier to read, because it can be taken in bite-sized chunks.

但它也倾向于创建相当大的依赖树,通过框架(特别是如果您使用约定)比手工管理要容易得多。今天我想在LINQPad中快速测试一些东西,我认为创建内核并在我的模块中加载太麻烦了,最后我手写了这个:

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());

回想起来,使用IoC框架会更快,因为模块按照约定定义了几乎所有这些东西。

Having spent some time studying the answers and comments on this question, I am convinced that the people who are opposed to using an IoC container aren't practicing true dependency injection. The examples I've seen are of practices that are commonly confused with dependency injection. Some people are complaining about difficulty "reading" the code. If done correctly, the vast majority of your code should be identical when using DI by hand as when using an IoC container. The difference should reside entirely in a few "launching points" within the application.

换句话说,如果你不喜欢IoC容器,你可能没有按照它应该做的方式来做依赖注入。

另一点:如果你在任何地方都使用反射,依赖注入真的不能手工完成。虽然我讨厌反射对代码导航造成的影响,但您必须认识到,在某些领域,它确实是无法避免的。ASP。例如,NET MVC尝试通过对每个请求的反射来实例化控制器。要手动进行依赖注入,你必须让每个控制器都成为“上下文根”,如下所示:

public class MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController()
    {
        _simpleMerger = new SimpleWorkflowInstanceMerger(
            new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
            new WorkflowAnswerRowUtil(
                new WorkflowFieldAnswerEntMapper(),
                new ActivityFormFieldDisplayInfoEntMapper(),
                new FieldEntMapper()),
            new AnswerRowMergeInfoRepository())
    }
    ...
}

现在将其与允许DI框架为你做这件事进行比较:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

使用DI框架,注意:

I can unit-test this class. By creating a mock ISimpleWorkflowInstanceMerger, I can test that it gets used the way I anticipate, without the need for a database connection or anything. I use far less code, and the code is much easier to read. If one of my dependency's dependency's changes, I don't have to make any changes to the controller. This is especially nice when you consider that multiple controllers are likely to use some of the same dependencies. I never explicitly reference classes from my data layer. My web application can just include a reference to the project containing the ISimpleWorkflowInstanceMerger interface. This allows me to break the application up into separate modules, and maintain a true multi-tier architecture, which in turn makes things much more flexible.

A typical web application will have quite a few controllers. All of the pain of doing DI by hand in each controller will really add up as your application grows. If you have an application with only one context root, which never tries to instantiate a service by reflection, then this isn't as big a problem. Nevertheless, any application that uses Dependency Injection will become extremely expensive to manage once it reaches a certain size, unless you use a framework of some kind to manage the dependency graph.

其他回答

IoC框架非常棒,如果你想…

…扔掉类型安全。许多(?)IoC框架迫使您执行代码,如果您想确保所有东西都正确连接。“嘿!希望我已经把一切都设置好了,这样我对这100个类的初始化就不会在生产中失败,抛出空指针异常!” ...在代码中使用全局变量(IoC框架都是关于改变全局状态的)。 ...编写依赖关系不明确、难以重构的蹩脚代码,因为你永远不知道什么依赖什么。

IoC的问题在于,使用它的人过去常常编写这样的代码

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

这显然是有缺陷的,因为Foo和Bar之间的依赖是硬连接的。然后他们意识到编写这样的代码会更好

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

这也有缺陷,但不那么明显。 在Haskell中,Foo()的类型将是IO Foo,但你真的不想要IO部分,这应该是一个警告信号,如果你得到了它,你的设计有问题。

为了摆脱它(io部分),获得ioc框架的所有优点,去掉它的所有缺点,你可以使用抽象工厂。

正确的解决方法应该是

data Foo = Foo { apa :: Bar }

或者

data Foo = forall b. (IBar b) => Foo { apa :: b }

和inject(但我不会叫它inject) Bar。

另外:观看Erik Meijer (LINQ的发明者)的视频,他说DI是为不懂数学的人准备的(我非常同意):http://www.youtube.com/watch?v=8Mttjyf-8P4

不像Spolsky先生,我不相信使用ioc框架的人很聪明——我只是认为他们不懂数学。

对我来说,使用IoC容器的最大好处(我个人使用Ninject)是消除了设置和其他类型的全局状态对象的传递。

我不为web编程,我的是一个控制台应用程序,在对象树的许多地方,我需要访问用户指定的设置或元数据,这些设置或元数据是在对象树的一个完全独立的分支上创建的。通过IoC,我简单地告诉Ninject将设置作为一个单例(因为它们总是只有一个实例),在构造函数中请求设置或字典,然后立即…它们在我需要的时候神奇地出现了!

如果不使用IoC容器,我将不得不通过2,3,…传递设置和/或元数据。在它被需要它的对象实际使用之前,有n个对象。

DI/IoC容器还有许多其他好处,正如其他人在这里详细介绍的那样,从创建对象到请求对象的想法可能会令人难以置信,但使用DI对我和我的团队非常有帮助,所以也许您可以将其添加到您的库中!

Ioc容器解决了一个你可能没有的问题,但这是一个很好的问题

http://kozmic.net/2012/10/23/ioc-container-solves-a-problem-you-might-not-have-but-its-a-nice-problem-to-have/

老实说,我没有发现有很多情况需要IoC容器,大多数情况下,它们只是增加了不必要的复杂性。

如果你只是用它来简化对象的构造,我要问,你是否在多个位置实例化了这个对象?单例不适合您的需要吗?您是否在运行时更改配置?(切换数据源类型等)。

如果是,那么您可能需要一个IoC容器。如果不是,那么您只是将初始化移到开发人员容易看到的地方。

谁说接口比继承好?假设您正在测试一个服务。为什么不使用构造函数DI,并使用继承创建依赖关系的模拟呢?我使用的大多数服务只有少数依赖项。以这种方式进行单元测试可以避免维护大量无用的接口,也意味着您不必使用Resharper来快速查找方法的声明。

我相信对于大多数实现来说,说IoC容器删除不需要的代码是一个神话。

首先,首先要设置容器。然后,您仍然必须定义需要初始化的每个对象。所以在初始化时不保存代码,而是移动它(除非你的对象被使用了不止一次)。单例是否更好?)然后,对于以这种方式初始化的每个对象,都必须创建和维护一个接口。

有人有什么想法吗?

就我个人而言,我使用IoC作为我的应用程序的某种结构图(是的,我也喜欢StructureMap;))。这使得在测试期间用Moq实现代替我通常的接口实现变得很容易。创建一个测试设置可以像对我的ioc -框架进行一个新的初始化调用一样简单,用一个mock代替任何一个测试边界类。

这可能不是IoC存在的目的,但这是我发现自己使用它最多的地方。