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

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

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

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


当前回答

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容器解决了一个你可能没有的问题,但这是一个很好的问题

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

我认为IoC的大部分价值都是通过使用DI获得的。既然你已经这样做了,剩下的好处是递增的。

你得到的值将取决于你正在处理的应用程序的类型:

For multi-tenant, the IoC container can take care of some of the infrastructure code for loading different client resources. When you need a component that is client specific, use a custom selector to do handle the logic and don't worry about it from your client code. You can certainly build this yourself but here's an example of how an IoC can help. With many points of extensibility, the IoC can be used to load components from configuration. This is a common thing to build but tools are provided by the container. If you want to use AOP for some cross-cutting concerns, the IoC provides hooks to intercept method invocations. This is less commonly done ad-hoc on projects but the IoC makes it easier.

我以前写过这样的功能,但如果我现在需要这些功能中的任何一个,我宁愿使用一个预先构建并经过测试的工具,如果它适合我的架构的话。

正如其他人所提到的,您还可以集中配置希望使用的类。虽然这可能是一件好事,但代价是误导和复杂化。大多数应用程序的核心组件都没有被替换,因此很难做出取舍。

我使用IoC容器,并欣赏其功能,但不得不承认我注意到了权衡:我的代码在类级别变得更加清晰,而在应用程序级别变得不那么清晰(即可视化控制流)。

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

想必没有人强迫您使用依赖注入容器框架。您已经使用DI来解耦类并改进可测试性,因此您将获得许多好处。简而言之,你喜欢简单,这通常是件好事。

如果您的系统达到了一个复杂的水平,手动DI变成了一件苦差事(也就是说,增加了维护),请将其与DI容器框架的团队学习曲线进行权衡。

如果您需要对依赖生命周期管理进行更多的控制(也就是说,如果您觉得需要实现单例模式),请查看DI容器。

如果使用DI容器,请只使用需要的特性。跳过XML配置文件,在代码中配置即可。坚持构造函数注入。Unity或StructureMap的基本内容可以压缩到几个页面中。

Mark Seemann写了一篇很棒的博文:什么时候使用DI容器

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

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

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