我已经使用依赖注入(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.

其他回答

关于Unity也是如此。如果房子太大,你会听到房梁的吱吱声。

当人们开始大谈IoC代码看起来有多干净时,我从不感到惊讶,这些人在90年代曾说过c++中的模板是如何优雅的方式,但现在却会谴责它们是神秘的。呸!

As you continue to decouple your classes and invert your dependencies, the classes continue to stay small and the "dependency graph" continues to grow in size. (This isn't bad.) Using basic features of an IoC container makes wiring up all these objects trivial, but doing it manually can get very burdensome. For example, what if I want to create a new instance of "Foo" but it needs a "Bar". And a "Bar" needs an "A", "B", and "C". And each of those need 3 other things, etc etc. (yes, I can't come up with good fake names :) ).

使用IoC容器为您构建对象图可以大大降低复杂性,并将其推到一次性配置中。我只需说“给我创建一个Foo”,它就会计算出构建一个Foo需要什么。

有些人将IoC容器用于更多的基础设施,这对于高级场景来说是很好的,但在这些情况下,我同意它会使新开发人员难以阅读和调试代码。

使用容器主要是将命令式/脚本化的初始化和配置风格改为声明式的。这可能会有一些不同的有益影响:

减少主程序启动的繁琐程序。 支持相当深入的部署时重新配置功能。 使依赖注入样式成为新工作阻力最小的路径。

当然,可能会有困难:

需要复杂启动/关闭/生命周期管理的代码可能不容易适应容器。 你可能需要处理个人、流程和团队文化方面的问题——但这就是你为什么要问…… 一些工具包本身正在迅速成为重量级的工具,鼓励了许多依赖注入容器开始反对的那种深度依赖。

我只是碰巧正在把自己生长的DI代码拉出来,并用IOC替换它。我可能已经删除了200多行代码,用大约10行代码替换了它。是的,我必须学习如何使用容器(Winsor),但我是一名在21世纪从事互联网技术工作的工程师,所以我已经习惯了。我大概花了20分钟看了一下怎么做。这很值得我花时间。

哇,真不敢相信乔尔会喜欢这个

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));

在这:

var svc = IoC.Resolve<IShippingService>();

许多人没有意识到你的依赖链可能会变成嵌套的,手动连接它们很快就会变得笨拙。即使使用工厂,重复代码也是不值得的。

IoC容器可以很复杂,是的。但是对于这个简单的例子,我已经证明了它非常简单。


好吧,让我们进一步证明这一点。假设您有一些想要绑定到智能UI的实体或模型对象。这个智能UI(我们称它为Shindows Morms)希望你实现INotifyPropertyChanged,这样它就可以进行更改跟踪并相应地更新UI。

"好吧,听起来没那么难"于是你开始写。

你可以这样开始:

public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

..结果是这样的:

public class UglyCustomer : INotifyPropertyChanged
{
    private string _firstName;
    public string FirstName
    {
        get { return _firstName; }
        set
        {
            string oldValue = _firstName;
            _firstName = value;
            if(oldValue != value)
                OnPropertyChanged("FirstName");
        }
    }

    private string _lastName;
    public string LastName
    {
        get { return _lastName; }
        set
        {
            string oldValue = _lastName;
            _lastName = value;
            if(oldValue != value)
                OnPropertyChanged("LastName");
        }
    }

    private DateTime _customerSince;
    public DateTime CustomerSince
    {
        get { return _customerSince; }
        set
        {
            DateTime oldValue = _customerSince;
            _customerSince = value;
            if(oldValue != value)
                OnPropertyChanged("CustomerSince");
        }
    }

    private string _status;
    public string Status
    {
        get { return _status; }
        set
        {
            string oldValue = _status;
            _status = value;
            if(oldValue != value)
                OnPropertyChanged("Status");
        }
    }

    protected virtual void OnPropertyChanged(string property)
    {
        var propertyChanged = PropertyChanged;

        if(propertyChanged != null)
            propertyChanged(this, new PropertyChangedEventArgs(property));
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

这是令人作呕的管道代码,我认为如果你手写这样的代码,你就是在从客户那里偷东西。有更好更聪明的工作方式。

听过“更聪明地工作,而不是更努力地工作”这句话吗?

想象一下,你团队里有个聪明人说:“这有个更简单的方法”

如果你让你的财产是虚拟的(冷静点,这没什么大不了的),那么我们就可以自动地编织财产行为。(这被称为AOP,但是不要担心它的名字,专注于它将为你做什么)

根据你使用的IoC工具,你可以这样做:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

噗!所有手动INotifyPropertyChanged BS现在都自动为您生成,在对象的每个虚拟属性setter上。

这是魔法吗?是的!如果您相信这段代码完成了它的工作,那么您就可以安全地跳过所有属性包装的繁文缛节。你有业务问题要解决。

IoC工具用于AOP的其他一些有趣的用法:

声明式和嵌套的数据库事务 声明性和嵌套的工作单元 日志记录 前/后条件(按合同设计)