我已经使用依赖注入(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容器(使用Martin Fowler描述的各种模式),并且正在询问为什么其他人的实现比您的更好。

所以,你有一堆已经工作的代码。并且想知道为什么要用其他人的实现来替换它。

考虑第三方IoC容器的优点

你可以免费修理bug 图书馆的设计可能比你的好 人们可能已经熟悉了特定的库 图书馆可能比你的快 它可能有一些您希望实现但没有时间实现的特性(您有服务定位器吗?)

Cons

你可以免费引入bug:) 图书馆的设计可能比你的还差 你必须学习一个新的API 太多你永远不会用到的功能 调试不是你写的代码通常比较困难 从以前的IoC容器迁移可能很乏味

所以,权衡利弊,做出决定吧。

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

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的其他一些有趣的用法:

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

我支持你,瓦迪姆。IoC容器采用了一个简单、优雅且有用的概念,并使其成为您需要用200页手册学习两天的东西。

我个人很困惑,为什么IoC社区把Martin Fowler写的一篇漂亮、优雅的文章变成了一堆复杂的框架,通常只有200-300页的手册。

我尽量不去评判(哈哈!),但我认为使用IoC容器的人(A)非常聪明,(B)对不如他们聪明的人缺乏同理心。对他们来说,每件事都很有意义,所以他们很难理解许多普通程序员会感到困惑的概念。这是知识的诅咒。理解IoC容器的人很难相信还有人不理解它。

The most valuable benefit of using an IoC container is that you can have a configuration switch in one place which lets you change between, say, test mode and production mode. For example, suppose you have two versions of your database access classes... one version which logged aggressively and did a lot of validation, which you used during development, and another version without logging or validation that was screamingly fast for production. It is nice to be able to switch between them in one place. On the other hand, this is a fairly trivial problem easily handled in a simpler way without the complexity of IoC containers.

我相信如果您使用IoC容器,您的代码将变得(坦白地说)难以阅读。为了弄清楚代码要做什么,您必须查看的地方的数量至少增加了一个。在天堂的某个地方,一位天使在呼喊。

不需要IoC容器。

但是如果您严格遵循依赖注入模式,您会发现使用一个依赖注入模式将会删除大量冗余的、无聊的代码。

无论如何,这通常是使用一个库/框架的最佳时机——当你了解它在做什么并且不需要库也能完成它的时候。

因为所有的依赖关系都是清晰可见的,所以它可以促进创建松散耦合的组件,同时在整个应用程序中易于访问和重用。