为什么我需要一个IoC容器而不是直接的DI代码?

Vad*_*dim 598 dependency-injection ioc-container inversion-of-control

我一直在使用依赖注入(DI)一段时间,在构造函数,属性或方法中注入.我从未觉得需要使用控制反转(IoC)容器.但是,我读的越多,我觉得社区使用IoC容器的压力就越大.

我使用.NET容器,如StructureMap,NInject,UnityFunq.我仍然没有看到IoC容器如何使我的代码受益/改进.

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

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

Ben*_*man 441

哇,简直不敢相信乔尔会赞成这个:

var svc = new ShippingService(new ProductLocator(), 
   new PricingService(), new InventoryService(), 
   new TrackingRepository(new ConfigProvider()), 
   new Logger(new EmailLogger(new ConfigProvider())));
Run Code Online (Sandbox Code Playgroud)

对此:

var svc = IoC.Resolve<IShippingService>();
Run Code Online (Sandbox Code Playgroud)

许多人没有意识到您的依赖链可能会嵌套,并且很快就会手动连接它们变得难以处理.即使有工厂,代码的重复也是不值得的.

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; }
}
Run Code Online (Sandbox Code Playgroud)

..结束了这个:

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;
}
Run Code Online (Sandbox Code Playgroud)

这是令人厌恶的管道代码,我坚持认为,如果你手工编写这样的代码,那么你就是在窃取你的客户端.有更好,更聪明的工作方式.

曾经听过这个词,工作更聪明,而不是更难?

想象一下,你团队中的一些聪明人出现说:"这是一种更简单的方法"

如果你让你的属性变得虚拟(冷静下来,这不是什么大不了的事),那么我们就可以自动编织这个属性行为了.(这称为AOP,但不要担心名称,请关注它将为您做什么)

根据您使用的IoC工具,您可以执行以下操作:

var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());
Run Code Online (Sandbox Code Playgroud)

噗! 所有手册INotifyPropertyChanged BS现在都可以在相关对象的每个虚拟属性设置器上自动生成.

这太神奇了吗? 是的!如果您可以相信这段代码能够完成它的工作,那么您可以安全地跳过所有包含mumbo-jumbo的属性.你有业务问题需要解决.

使用IoC工具进行AOP的其他一些有趣的用途:

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

  • @overstood - 我完全不同意.首先,在这个例子中,清晰度在哪里?最近的消费者.消费者明确要求INotifyPropertyChanged.仅仅因为我们*可以用微代码或宏代码生成这种类型的代码,并不意味着编写代码是个好主意.INotifyPropertyChanged gunk只是死记硬背,世俗,管道,实际上有损于所讨论类的可读性. (39认同)
  • 标记为因为您混淆了依赖注入模式和服务定位器模式.前者意味着用来代替后者.例如,一个对象(或整个系统)永远不应该调用Resolve(...)来获取IShippingService的实例.需要IShippingService的对象应该接收对构造它们时应该使用的实例的引用,而某些更高层负责将这两个对象连接在一起. (31认同)
  • 无论是什么(IoC,DynamicProxy,AOP或*black magic*),有人可以将我链接到框架中的具体文章/特定文档,其中提供了实现此目的的更多详细信息吗? (30认同)
  • 哎呀,你忘了把所有的属性都虚拟化,但它没有用.您需要花时间从客户端调试此窃取吗?(如果您不使用DynamicProxy,请道歉.:P) (28认同)
  • 使用INotifyPropertyChanged包装器可以牺牲很多清晰度.如果你开始编写那个管道需要花费几分钟的时间,那么你就错了.当涉及代码的冗长时,少即是多! (17认同)
  • Ben - 虽然我同意你对I​​oC/DI的评论,但我认为这个例子 - var bindingFriendlyInstance = IoC.Resolve <Customer>(new NotifyPropertyChangedWrapper()); 似乎是完全错误的.我看不到任何情况,你的IoC容器可以用INotify返回一个Customer对象..实现没有别的东西声明"gunk"你究竟怎么实际实现这个? (11认同)
  • 我需要同意Chris Webb,"NotifyPropertyChangedWrapper"看起来像什么? (9认同)
  • Jamie - 真正的工作通常由DynamicProxy之类的东西完成,它允许您轻松拦截对象上的(虚拟或接口)成员的调用.这与IoC几乎没有关系 - 事实上,在上面的例子中,我们无论如何都在谈论一个实体,所以你永远不会从容器中解析它.您必须使用完全独立的ORM进行清理.我不想听起来反IoC - 我在我当前的项目中使用了Castle和大量的自定义工具,但上面的例子是一个很差的(它没有按照书面形式或描述的方式工作). (7认同)
  • IoC!= AOP.IoC旨在消除对象/服务之间的硬连线依赖性,而AOP旨在分离封装良好的模块中的问题,并自动"编织"所有这些问题.前者可用于执行后者,但还有其他(可能更好)的替代方案,如预处理或动态代码生成.在我看来,如果你选择使用IoC容器,因为它允许你做AOP,你没有做出正确的选择:更好的工具(参见众多AOP框架,如PostSharp,AspectC++,AspectJ等) ). (5认同)
  • 公平地说,我的评论在编辑之前就考虑了答案的前半部分.你使用NotifyPropertyChanged包装器去了一点(这不是常见的情况,尽管很酷). (4认同)
  • @ChrisWebb有人非常有帮助[问这是一个新的SO问题](http://stackoverflow.com/questions/4435271/cruft-code-ioc-to-the-rescue)并得到一个有用的答案,万一你是仍然感兴趣. (4认同)
  • 另外,您指的是哪种IoC工具? (3认同)
  • 我感兴趣的是什么工具知道如何自动注入代码以作为NotifyPropertyChangedWrapper()工作,尽管你的例子被设计得特别差 - 如果你在每个属性周围使用Observable包装器那么你可以使代码显着更清晰使用IoC容器(或任何NotifyPropertyChangedWrapper()是). (3认同)
  • @Nat:我不会混淆两者.我正在阐述IoC工具的强大功能.最终你必须向容器询问一个物体,理想情况下,这种情况会发生在最前沿,随后的注射会发生在你身上. (3认同)
  • 优秀的添加.我经常发现自己正在为一个物体做那种管道.我现在将为IoC敞开心扉,看看它可以解释什么. (2认同)
  • 实际上我们正在使用IoC.它并没有像我想的那样让每个人都了解IoC. (2认同)
  • 我对这个答案感到困惑.这里有一个我无法找到的答案,说实体不应该由DIC构建.[这一个](http://stackoverflow.com/questions/2504798/dependency-injection-in-constructors/2505059#2505059)说你的应用程序应该分离到一个构造和一个执行部分,而DIC应该永远不会出现在执行部分,使其无法用它来创建实体. (2认同)

Joe*_*sky 138

我和你在一起,瓦迪姆.IoC容器采用简单,优雅和有用的概念,并使用200页的手册使您需要学习两天.

我个人很困惑IoC社区如何阅读Martin Fowler撰写的一篇优美,优雅的文章,并将其转化为一系列复杂的框架,通常包含200-300页的手册.

我尽量不做评判(哈哈!),但我认为使用IoC容器的人是(A)非常聪明,(B)对那些不那么聪明的人缺乏同情心.一切都对他们完全有意义,所以他们很难理解许多普通程序员会发现这些概念令人困惑.这是知识诅咒.了解IoC容器的人很难相信有些人不理解它.

使用IoC容器最有价值的好处是,您可以在一个位置使用配置开关,这样您就可以在测试模式和生产模式之间进行切换.例如,假设您有两个版本的数据库访问类...一个版本进行了积极记录并进行了大量验证(在开发过程中使用),另一个版本没有记录或验证,这对于生产来说非常快.很高兴能够在一个地方之间切换它们.另一方面,这是一个相当简单的问题,可以在没有IoC容器复杂性的情况下以更简单的方式轻松处理.

我相信如果你使用IoC容器,坦率地说,你的代码很难阅读.您必须查看以确定代码尝试执行的操作的位置数量至少增加一个.在天堂的某个地方,一个天使喊出来.

  • 我觉得你的事实和乔尔有点混淆了.首先是IoC和容器,然后是Martin的论文,反之亦然. (81认同)
  • 乔尔我曾经和你一样思考.然后我真的花了五分钟时间弄清楚如何让最基本的设置运行,并对80/20的规则感到惊讶.它使代码更加清晰,特别是在简单的情况下. (58认同)
  • 大多数容器都可以让你快速前进.使用autofac,结构图,统一,ninject等,您应该能够让容器在大约5分钟内工作.是的,他们有先进的功能,但你不需要这些功能. (55认同)
  • 我已经编程了不到两年,我阅读了Ninject wiki并得到了它.从那以后,我一直在很多地方使用DI.你看到了吗?不到两年,在维基上阅读几页.我不是巫师或任何东西.我不认为自己是个天才,但我很容易理解.我无法想象有人真正理解OO无法掌握它.另外,你指的是200-300页的手册?我从未见过那些人.我使用的所有IoC容器都非常简短. (55认同)
  • +1写得好,经过深思熟虑.我认为很多人没有意识到的一件事是,添加一个框架或类似的"神奇地"处理某些东西会自动增加系统的复杂性和局限性.有时它是值得的,但往往会忽略某些东西,并且熵会被释放到代码中,试图解决由限制强制执行的问题.它可以预先节省时间,但人们必须意识到可能需要花费100倍才能解决一些模糊不清的问题,只有少数人真正有足够的知识来解决. (35认同)
  • 在Joel的辩护中,在Z80 Assembly中或在多线程代码中使用带有COM的模板时,DI和IoC可能很难掌握. (28认同)
  • 我不知道你们为什么质问乔尔.他知道我们头脑中发生了什么,这个家伙就像圣诞老人一样.他知道我们无法理解IoC,而且我们可以在没有帮助的情况下在早上穿上裤子.谢谢Joel提醒我,我是多么愚蠢,我对自己的态度太过分了. (24认同)
  • 我完全赞同乔尔.当一个更简单的解决方案让你盯着你的脸时,看起来像是一个非常多的肚脐放牧.人们倾向于忘记KISS原则,这是一个非常需要应用的案例. (24认同)
  • KISS表示,有一些评论家支持Joel的观点.这些人没有意识到的是使用和IoC容器是KISS方式.KISS代表"保持简单,愚蠢",而不是"从简单开始,最终变得一团糟,愚蠢"(SOSEUWMS).IoC容器可帮助您随着时间的推移保持应用程序的简单性.我确信Joel是SOSEUWMS的粉丝,因为它有助于保持bug追踪器的高需求. (24认同)
  • 怎么样:http://ninject.codeplex.com/Wiki/View.aspx?title = Module%20and%20the%20Kernel &referringTitle = Home被认为是火箭科学超越我. (22认同)
  • 另一方面,我认为自己非常聪明,虽然我确实花了很多时间去研究和体验IoC - 但我仍然讨厌它.带着*激情*.但这可能只是因为我花了大部分时间实际上*读取*代码,而不只是生成只写代码....这是IoC真正发光的地方,编写一小部分代码,然后将它们组合起来并以不同方式重新组合它们在配置时.另一方面,代码可读性在IoC中获得巨大成功. (20认同)
  • 我使用的是IOC容器而且我不聪明. (18认同)
  • 如果我可以说,对于不同的人来说,它看起来就像世界的不同观点.使用国际奥委会的人就像想要世界跟随他们的脚步的思想家一样,然后还有其他像我(和其他开发者)一样被困在意识形态和现实之间;) (15认同)
  • Martin Fowler虽然是一位出色的作家,却没有以任何方式创造IoC. (14认同)
  • @Ryan Emerle:在我看来,在使用IoC并认为应该普遍使用IoC框架之间存在差异. (14认同)
  • 事实上,这个答案是SE上最具争议性的答案,这暗示了Ioc/DI已经变成了多少时尚(好或坏).我喜欢乔尔的回答,因为他提供了一些实践智慧; YAGNI.当然有些项目保证IoC,当然其他人没有.它是保持腰带的好工具,但不要用大锤来悬挂相框...... (14认同)
  • 答案证明,几乎有一半的社区认为downvote按钮是"我不同意",而不是"这个答案没有帮助".每当我面对我们只是复杂的灵长类动物时,我讨厌它. (13认同)
  • 当正确完成时,IOC很好,但是当有人搞砸了它时,它真的很可怕,最终有人会搞砸它.它还可以通过一些代码来准确地模糊正在发生的事情.恕我直言,如果你能看到一些代码并在几秒钟内得到正在发生的事情而不需要挖掘剩余的源代码,那总是更好. (9认同)
  • 就像在很多情况下一样,你试图保护开发人员免受强大的想法只会最终光顾他们并贬低他们的能力.为了让您的观点更加明确,您必须*提供一个带有200页手册的IoC容器示例 - 我根本不相信有一个!这让你的逆势修辞变得不那么美味了.为了练习IoC/DI,必须学习这些技术所解决的问题.根据这种理解,您可以使用任何工具,或者不使用任何工具.没有它,使用什么工具并不重要. (9认同)
  • +1进行健全性检查以降低代码复杂性 (9认同)
  • 哇,最有争议的帖子......只是因为乔尔敢于暗示IoC不是所有发展的全部.老实说,已经足够宗教战争了!即使是IF(很大的if)IoC是自切片黄油以来最好的东西,即使任何半学习的程序员在学术界之外有超过几周的实际经验也无法相信它是所有 - 甚至大多数 - 情况下的最佳解决方案. (9认同)
  • 不能同意更多.开始研究使用DI框架(StructureMap),它声称是最古老的IoC容器.考虑到我想要的东西,复杂性是惊人的.相反,我正在编写自己非常简单的服务定位器.也许其他一个IoC容器会更有意义,但我真的没有看到这一点,也没有花几天的时间来弄清楚如何使用会使代码阅读变得更复杂的东西. (7认同)
  • 乔尔 - 我认为你有一段时间没有看过IoC Containers,因为在过去的几年里,分水岭肯定已经过去了.它们现在很容易配置,主要是因为惯例.DI很好; 使实践更容易实施和维护.当第三方库可以为您完成时,为什么要传递代码的具体实现?IoC Container将:管理整个图形; 不必为穷人提供课程; 减少代码行; 将您的依赖项解析集中在代码库中的一个简洁位置. (7认同)
  • Joel - 您声称IoC的主要好处是交换测试实现,表明您不了解IoC.IoC是关于分离关注点,让您在添加/删除依赖关系时不必重新编写所有实例化链.更不用说其他许多好处了 (7认同)
  • 我是IoC容器的粉丝,但我绝不认为它们"易于获取"(这些概念非常抽象,并且是软件开发的核心)......而且我认为Joel的答案是鼓励人们去忽略IoC背后的重要思想因此我的downvote. (6认同)
  • 我真的不明白我是如何确信来到StackOverflow DevDays的 (6认同)
  • 因此,如果您不使用IoC容器,那是因为您不够聪明,无法理解它们,但如果您确实使用IoC容器(并喜欢它的每一秒),那是因为您太聪明了? (5认同)
  • 此外,如果有人正在编写可测试的代码,遵循SRP等(我认为大多数人都同意这是好的)你的班级基本上*总是*最终有大量的接口依赖.我问:如果没有IoC框架,你怎么连接所有课程? (4认同)
  • 毫无疑问,在没有容器的IoC之后,比"阅读200页手册"更加复杂和耗时.更不用说容器是在福勒先生的文章之前出现的,并且是设计模式的一部分.因此,如果Spolsky先生想要声称IoC过于复杂,我们可能不得不同意不同意,但因为那不是他所说的......他只是完全错了. (4认同)
  • @ Joel-spolsky我不明白为什么你认为这会让代码更难阅读.代码实际上更容易理解.所有依赖项都是通过构造函数注入的.不需要跟踪依赖项.根据您需要创建的项目数量,需要大约2-3行代码进行设置. (4认同)
  • @Joel你知道这是网站上最有争议的答案吗?:) http://data.stackexchange.com/stackoverflow/s/87/most-controversial-posts-on-the-site (4认同)
  • 并不是我难以相信有些人不理解IoC,而是我难以相信有些人不会试图理解.真的,这并不难.我认为最大的问题是名字听起来令人生畏. (3认同)
  • 这个答案有一定的道理,因为我所看到的关于DI的大多数文章的作者似乎完全无法用诉诸大量的行话来解释这个概念.他们为已经了解DI的人写了DI的解释.他们不会为不懂DI而且需要学习的人写作. (3认同)
  • IoC:隐藏复杂性而不是解决它(另一个答案中的垃圾构造函数?它仍然存在); 运行错误导致运行时错误; 尽可能地进行难以测试和难以发现的错误(组件注册错误); 赞成神类("内核").在大多数IoC代码中很容易发现所有这些问题.令人不安的是,许多人认为这是一个很好的代码. (3认同)
  • 我之前使用过结构图和Unity.您可以非常简单地完成基本操作(几行代码,阅读他们的常见问题解答),但如果您尝试将其置于太多情况下,则会很快变得复杂. (2认同)
  • 哇... Geez谈论强烈反对.我认为这是一种如何不对抗人群的实验.老实说,我没有长时间开发(现在差不多3年),我使用了3个不同的.Net IoC容器.我并不声称理解他们所做的一切.但我知道他们已经简化了我的发展.以同样的方式,我使用SQL Server.但我不会声称理解现代RDBMS系统的所有复杂性. (2认同)
  • @KevDog:我完全同意姓名问题.依赖注入容器完全听起来像Dalek发明的东西. (2认同)
  • 如果您正在处理数百个组件的连接并希望采用集中管理依赖关系的方式,那么IoC容器可以让生活变得更加轻松.我对使用容器持怀疑态度,因为我没有看到好处.我现在确信它们是有用的套件.我们通过配置交换对象图构造的大部分,并在不同的上下文中重用我们的模块(这是我们广泛做的事情,它不仅仅是一个推测性的管道梦想).权衡是不熟悉新概念并牺牲一些编译时间的安全性. (2认同)

Tru*_*ill 37

据推测,没有人强迫你使用DI容器框架.您已经在使用DI来分离类并提高可测试性,因此您可以获得许多好处.简而言之,你喜欢简单,这通常是一件好事.

如果您的系统达到复杂程度,手动DI成为一项杂务(即增加维护),请根据DI容器框架的团队学习曲线进行权衡.

如果您需要更多地控制依赖关系生存期管理(也就是说,如果您觉得需要实现Singleton模式),请查看DI容器.

如果您使用DI容器,请仅使用您需要的功能.如果足够,请跳过XML配置文件并在代码中对其进行配置.坚持构造函数注入.Unity或StructureMap的基础知识可以缩减为几页.

Mark Seemann有一篇很棒的博客文章:何时使用DI容器


ben*_*wey 32

在我看来,IoC的首要好处是能够集中依赖项的配置.

如果您当前正在使用依赖注入,则代码可能如下所示

public class CustomerPresenter
{
  public CustomerPresenter() : this(new CustomerView(), new CustomerService())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}
Run Code Online (Sandbox Code Playgroud)

如果你使用静态IoC类,而不是恕我直言,更令人困惑的配置文件,你可以有这样的东西:

public class CustomerPresenter
{
  public CustomerPresenter() : this(IoC.Resolve<ICustomerView>(), IoC.Resolve<ICustomerService>())
  {}

  public CustomerPresenter(ICustomerView view, ICustomerService service)
  {
    // init view/service fields
  }
  // readonly view/service fields
}
Run Code Online (Sandbox Code Playgroud)

然后,你的静态IoC类看起来像这样,我在这里使用Unity.

public static IoC
{
   private static readonly IUnityContainer _container;
   static IoC()
   {
     InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerView, CustomerView>();
      _container.RegisterType<ICustomerService, CustomerService>();
      // all other RegisterTypes and RegisterInstances can go here in one file.
      // one place to change dependencies is good.
   }
}
Run Code Online (Sandbox Code Playgroud)

  • 您应该利用IOC容器为您构造对象的能力,而不是在默认构造函数中执行IoC.Resolve <T>.摆脱那个空构造函数,让IOC容器为你构建对象.var presenter = IoC.Resolve <CustomerPresenter>(); 瞧!所有依赖关系已经连线. (23认同)
  • 这种配置集中化的缺点是知识的去语境化.乔尔指出这个问题,"坦率地说,代码变得更难阅读". (12认同)
  • Ben,你所说的实际上是服务定位,而不是依赖注入 - 这是有区别的.我总是喜欢第一个而不是后者.http://stevenharman.net/blog/archive/2009/09/25/prefer-dependency-injection-to-service-location.aspx (11认同)
  • 史蒂夫,我知道.NET文本并走了很多年.我也熟悉其他模式和形式,我从内心深处理解存在真正的权衡.我知道如何以及何时做出这些权衡,但我工作生活的多样性至少让我明白了Joel的观点.而不是向我讲授我比大多数.NET单一文化学家使用更长时间的工具和技巧,告诉我一些我不知道的事情.我保留了我的大脑堆栈,用于多样化的批判性思维,而不是alt.net stasis和orthodoxy. (7认同)
  • @sbellware,有什么知识?被视为原始依赖关系的接口是一个契约,应该足以传达它的目的和行为,不是吗?我想你可能指的是从处理合同执行中获得的知识,在这种情况下你需要追踪适当的配置?我依靠计算机(VS中的R#,IntelliJ等),因为他们非常擅长导航图形的节点和边缘; 我保留了我的大脑堆栈,用于我目前关注的工作网站的上下文. (6认同)

ben*_*wey 32

IoC容器也适用于加载深层嵌套的类依赖项.例如,如果您使用Depedency Injection获得以下代码.

public void GetPresenter()
{
    var presenter = new CustomerPresenter(new CustomerService(new CustomerRepository(new DB())));
}

class CustomerPresenter
{
    private readonly ICustomerService service;
    public CustomerPresenter(ICustomerService service)
    {
        this.service = service;
    }
}

class CustomerService
{
    private readonly IRespository<Customer> repository;
    public CustomerService(IRespository<Customer> repository)
    {
        this.repository = repository;
    }
}

class CustomerRepository : IRespository<Customer>
{
    private readonly DB db;
    public CustomerRepository(DB db)
    {
        this.db = db;
    }
}

class DB { }
Run Code Online (Sandbox Code Playgroud)

如果您已将所有这些依赖项加载到IoC容器中,则可以解析CustomerService,并且所有子依赖项将自动得到解析.

例如:

public static IoC
{
   private IUnityContainer _container;
   static IoC()
   {
       InitializeIoC();
   }

   static void InitializeIoC()
   {
      _container = new UnityContainer();
      _container.RegisterType<ICustomerService, CustomerService>();
      _container.RegisterType<IRepository<Customer>, CustomerRepository>();
   }

   static T Resolve<T>()
   {
      return _container.Resolve<T>();
   }
}

public void GetPresenter()
{
   var presenter = IoC.Resolve<CustomerPresenter>();
   // presenter is loaded and all of its nested child dependencies 
   // are automatically injected
   // -
   // Also, note that only the Interfaces need to be registered
   // the concrete types like DB and CustomerPresenter will automatically 
   // resolve.
}
Run Code Online (Sandbox Code Playgroud)


Bil*_*win 28

我是声明性编程的粉丝(看看我回答了多少个SQL问题),但我看过的IoC容器对于他们自己的好处似乎太神秘了.

...或者IoC容器的开发人员可能无法编写清晰的文档.

......或者两者都适用于某种程度.

我不认为IoC容器的概念是坏的.但实现必须足够强大(即灵活),足以在各种应用程序中使用,但简单易懂.

它可能是六个中的六个,另外六个.真正的应用程序(不是玩具或演示)必然是复杂的,可以解决许多极端情况和规则异常.要么将这种复杂性封装在命令式代码中,要么将其封装在声明性代码中.但你必须在某个地方代表它.

  • 我喜欢你对不可避免的复杂性的观察.基于容器的连接和手动依赖连接之间的区别在于,使用容器,您可以单独关注每个组件,从而以相当线性的方式管理日益增加的复杂性.具有手动依赖性布线的系统更难以发展,因为配置一个组件的行为很难与其他所有组件的配置隔离. (5认同)
  • 我当然理解你的答案,但我认为问题的很大一部分是很多IoC框架(以及其他很多其他框架)也被描述和记录给其他已经非常熟悉的框架.必要的概念和术语.我正在学习很多这样的东西,只是继承了另一个程序员的代码.当'对象图'相当小并且代码没有真正的测试覆盖时,我很难理解为什么代码使用IoC.作为SQL程序员,您可以更好地欣赏使用IoC和ORM. (2认同)

Jef*_*tin 27

使用容器主要是从命令式/脚本式初始化和配置方式转换为声明式方式.这可能有一些不同的有益效果:

  • 减少毛球主程序启动例程.
  • 启用相当深的部署时重新配置功能.
  • 使依赖注入风格成为新工作阻力最小的途径.

当然,可能有困难:

  • 需要复杂启动/关闭/生命周期管理的代码可能不容易适应容器.
  • 您可能需要浏览任何个人,流程和团队文化问题 - 但是,这就是您问...的原因
  • 一些工具包本身正迅速变得重量级,鼓励了许多DI容器开始时的强烈依赖性作为反对.


Sam*_*ron 23

听起来像你已经建立了自己的IoC容器(使用Martin Fowler描述的各种模式)并且问为什么别人的实现比你的更好.

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

考虑第三方IoC容器的优点

  • 你得到免费修复的bug
  • 图书馆设计可能比你的更好
  • 人们可能已经熟悉特定的图书馆
  • 图书馆可能比你的快
  • 它可能有一些你希望你实现但没有时间的功能(你有一个服务定位器吗?)

缺点

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

所以,权衡你的利弊与你的利弊并做出决定.

  • 我不同意.IOC是一种做DI的方式.IOC容器通过使用动态运行时反射来控制类型实例化以满足和注入依赖关系来执行DI.OP建议他正在进行静态DI,并且正在思考为什么他需要权衡编译时检查的好处,以获得通常与IOC动态运行时反射相关的好处. (3认同)

Ste*_*ons 17

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

您获得的值取决于您正在处理的应用程序类型:

  • 对于多租户,IoC容器可以处理一些用于加载不同客户端资源的基础结构代码.当您需要特定于客户端的组件时,请使用自定义选择器来处理逻辑,而不必担心客户端代码.你当然可以自己构建这个,但这里有一个IoC如何提供帮助的例子.

  • 有许多可扩展点,IoC可用于从配置加载组件.这是常见的构建方法,但容器提供了工具.

  • 如果您想将AOP用于某些横切关注点,IoC提供了拦截方法调用的钩子.这在项目上不太常见,但IoC使其更容易.

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

如其他人所述,您还可以集中配置要使用的类.虽然这可能是一件好事,但它的代价是误导和复杂化.大多数应用程序的核心组件都没有被替换太多,因此需要进行权衡取舍.

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


Max*_*007 16

我是一名康复的国际奥委会成瘾者.这些天我发现很难证明在大多数情况下使用IOC来证明其合理性.IOC容器牺牲了编译时检查,据说可以为您提供"简单"设置,复杂的生命周期管理以及在运行时动态发现依赖关系.我发现编译时间检查的丢失以及由此导致的运行时间魔术/异常,在绝大多数情况下都不值得花里胡哨.在大型企业应用程序中,他们可能很难跟踪正在发生的事情.

我不买集中论证,因为你可以很容易地集中静态设置,为你的应用程序使用抽象工厂,并虔诚地将对象创建推迟到抽象工厂,即做适当的DI.

为什么不这样做静态无魔法DI:

interface IServiceA { }
interface IServiceB { }
class ServiceA : IServiceA { }
class ServiceB : IServiceB { }

class StubServiceA : IServiceA { }
class StubServiceB : IServiceB { }

interface IRoot { IMiddle Middle { get; set; } }
interface IMiddle { ILeaf Leaf { get; set; } }
interface ILeaf { }

class Root : IRoot
{
    public IMiddle Middle { get; set; }

    public Root(IMiddle middle)
    {
        Middle = middle;
    }

}

class Middle : IMiddle
{
    public ILeaf Leaf { get; set; }

    public Middle(ILeaf leaf)
    {
        Leaf = leaf;
    }
}

class Leaf : ILeaf
{
    IServiceA ServiceA { get; set; }
    IServiceB ServiceB { get; set; }

    public Leaf(IServiceA serviceA, IServiceB serviceB)
    {
        ServiceA = serviceA;
        ServiceB = serviceB;
    }
}


interface IApplicationFactory
{
    IRoot CreateRoot();
}

abstract class ApplicationAbstractFactory : IApplicationFactory
{
    protected abstract IServiceA ServiceA { get; }
    protected abstract IServiceB ServiceB { get; }

    protected IMiddle CreateMiddle()
    {
        return new Middle(CreateLeaf());
    }

    protected ILeaf CreateLeaf()
    {
        return new Leaf(ServiceA,ServiceB);
    }


    public IRoot CreateRoot()
    {
        return new Root(CreateMiddle());
    }
}

class ProductionApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new ServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new ServiceB(); }
    }
}

class FunctionalTestsApplication : ApplicationAbstractFactory
{
    protected override IServiceA ServiceA
    {
        get { return new StubServiceA(); }
    }

    protected override IServiceB ServiceB
    {
        get { return new StubServiceB(); }
    }
}


namespace ConsoleApplication5
{
    class Program
    {
        static void Main(string[] args)
        {
            var factory = new ProductionApplication();
            var root = factory.CreateRoot();

        }
    }

    //[TestFixture]
    class FunctionalTests
    {
        //[Test]
        public void Test()
        {
            var factory = new FunctionalTestsApplication();
            var root = factory.CreateRoot();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

您的容器配置是您的抽象工厂实现,您的注册是抽象成员的实现.如果需要新的单例依赖项,只需将另一个抽象属性添加到抽象工厂即可.如果您需要瞬态依赖,只需添加另一个方法并将其作为Func <>注入.

好处:

  • 所有设置和对象创建配置都是集中的.
  • 配置只是代码
  • 编译时间检查使您易于维护,因为您不会忘记更新您的注册.
  • 没有运行时反射魔法

我建议怀疑论者给它一个下一个绿色的田野项目,并诚实地问自己,你需要哪个容器.稍后您可以轻松地将IOC容器考虑在内,因为您只是使用IOC容器配置模块替换工厂实现.


Jef*_*ron 14

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

我没有为web编程,我是一个控制台应用程序,在对象树深处的许多地方,我需要访问在对象树的完全独立的分支上创建的用户指定的设置或元数据.使用IoC,我只是告诉Ninject将设置视为一个单例(因为它们总是只有一个实例),请在构造函数中请求设置或字典并预先设置......当我需要它们时它们会神奇地出现!

在不使用IoC容器的情况下,我必须将设置和/或元数据向下传递到2,3,...,n个对象,然后才能被需要它的对象使用.

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

  • 没错,但是IoC容器对应用程序的其余部分是透明的.你在启动时调用它一次来获取一个实例然后就是你看到的最后一个实例.对于全局对象,您的代码充满了硬依赖性 (3认同)
  • 这是一个巨大的优势.不敢相信其他人之前没有提到它.无需传递或存储临时对象,使代码更清晰. (2认同)
  • @JeffreyCameron IoC Container*是*全局对象.它不会删除您的全局依赖项. (2认同)

fin*_*son 14

如果你想要IoC框架是很好的...

  • ......丢掉类型安全.许多(所有?)IoC框架强制您执行代码,如果您想确定所有内容都已正确连接."嘿!希望我已经完成了所有设置,因此我对这100个类的初始化不会在生产中失败,抛出空指针异常!"

  • ...您的垃圾用全局代码(IoC框架是所有有关更改全局状态).

  • ...编写糟糕的代码与不清楚的依赖,这很难重构,因为你永远不知道什么取决于什么.

IoC的问题在于使用它们的人习惯于编写这样的代码

public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}
Run Code Online (Sandbox Code Playgroud)

这也有缺陷,但不太明显.在Haskell中,类型Foo()会是,IO Foo但你真的不想要IO-part而且应该是一个警示标志,如果你得到它,你的设计有问题.

要摆脱它(IO部分),获得IoC框架的所有优点,并且没有任何缺点,你可以改为使用抽象工厂.

正确的解决方案就是这样的

data Foo = Foo { apa :: Bar }
Run Code Online (Sandbox Code Playgroud)

或者可能

data Foo = forall b. (IBar b) => Foo { apa :: b }
Run Code Online (Sandbox Code Playgroud)

和注入(但我不会称之为注入)吧.

另外:与Erik Meijer(LINQ的发明者)一起观看此视频,他说DI适用于不懂数学的人(我不能同意):http://www.youtube.com/watch?v = 8Mttjyf-8P4

与Spolsky先生不同,我不相信使用IoC框架的人非常聪明 - 我只是相信他们不懂数学.

  • 抛弃类型安全 - 除了你的例子仍然是类型安全的,这就是接口.您不是传递对象类型,而是传递接口类型.并且类型安全无论如何都不会消除空引用异常,您可以将null引用作为类型安全参数传递 - 这根本不是类型安全问题. (9认同)
  • 我在某种程度上同意你的第一点,我想对第二和第三点的一些解释对我来说从来都不是问题.您的C#示例使用IoC容器作为服务定位器,这是一个常见的错误.比较C#vs Haskell = apples vs oranges. (2认同)
  • 你不要丢掉类型安全.一些(如果不是大多数)DI容器允许您在注册过程后验证完整的对象图.执行此操作时,您可以阻止应用程序在错误配置(快速失败)时启动,并且我的应用程序总是有一些单元测试调用生产配置以允许我在测试期间发现这些错误.我建议大家使用IoC容器编写一些单元测试,以确保可以解析所有顶级对象. (2认同)

Str*_*ior 10

我发现正确实现依赖注入往往会迫使程序员使用各种其他编程实践来帮助提高代码的可测试性,灵活性,可维护性和可伸缩性:单一责任原则,关注点分离和编码等实践反对API.感觉就像我被迫编写更多模块化,一口大小的类和方法,这使得代码更容易阅读,因为它可以采用一口大小的块.

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

var merger = new SimpleWorkflowInstanceMerger(
    new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName), 
    new WorkflowAnswerRowUtil(
        new WorkflowFieldAnswerEntMapper(),
        new ActivityFormFieldDisplayInfoEntMapper(),
        new FieldEntMapper()),
    new AnswerRowMergeInfoRepository());
Run Code Online (Sandbox Code Playgroud)

回想起来,使用IoC框架本来会更快,因为模块按惯例定义了所有这些东西.

花了一些时间研究这个问题的答案和评论,我确信那些反对使用IoC容器的人并没有练习真正的依赖注入.我见过的例子是与依赖注入混淆的做法.有些人抱怨难以"阅读"代码.如果操作正确,当使用DI时,绝大多数代码应该与使用IoC容器时相同.差异应该完全在应用程序中的几个"启动点".

换句话说,如果你不喜欢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())
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

现在将其与允许DI框架为您进行比较:

public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}
Run Code Online (Sandbox Code Playgroud)

使用DI框架,请注意:

  • 我可以对这门课进行单元测试.通过创建一个模拟ISimpleWorkflowInstanceMerger,我可以测试它是否按照我预期的方式使用,而不需要数据库连接或任何东西.
  • 我使用的代码少得多,而且代码更容易阅读.
  • 如果我的依赖项之一的依赖项发生了变化,我不必对控制器进行任何更改.当您考虑多个控制器可能使用某些相同的依赖项时,这一点尤其好.
  • 我从未明确引用数据层中的类.我的Web应用程序可以只包含对包含该ISimpleWorkflowInstanceMerger接口的项目的引用.这使我可以将应用程序分解为单独的模块,并维护真正的多层体系结构,从而使事情更加灵活.

典型的Web应用程序将拥有相当多的控制器.在每个控制器中手动执行DI的所有痛苦都会随着应用程序的增长而增加.如果您的应用程序只有一个上下文根,它从不尝试通过反射实例化服务,那么这不是一个大问题.然而,任何使用依赖注入的应用程序一旦达到一定的大小就会变得非常昂贵,除非你使用某种框架来管理依赖图.


mat*_*rns 9

每当你使用"new"关键字时,你就会创建一个具体的类依赖关系,并且你的脑子里会出现一个小闹铃.单独测试这个对象变得更加困难.解决方案是编程接口并注入依赖关系,以便可以使用实现该接口的任何东西(例如,模拟)对对象进行单元测试.

麻烦的是你必须在某处构建对象.工厂模式是将耦合移出POXO的一种方法(Plain Old"在此处插入您的OO语言"对象).如果您和您的同事都在编写这样的代码,那么IoC容器就是您可以对代码库进行的下一个"增量改进".它会将所有令人讨厌的Factory样板代​​码移出干净的对象和业务逻辑.他们会得到它并喜欢它.哎呀,让公司谈谈你为什么喜欢它并让每个人都热情好客.

如果你的同事还没有做DI,那么我建议你先关注它.传播如何编写易于测试的干净代码.清洁DI代码是困难的部分,一旦你在那里,将对象布线逻辑从Factory类转移到IoC容器应该是相对微不足道的.


bbm*_*mud 8

由于所有依赖项都清晰可见,因此它可以促进创建松散耦合的组件,同时可以在整个应用程序中轻松访问和重用.


kyo*_*ryu 7

不需要 IoC容器.

但是如果你严格遵循DI模式,你会发现有一个会删除大量冗余,无聊的代码.

无论如何,这通常是使用库/框架的最佳时间 - 当您了解它正在做什么并且可以在没有库的情况下完成它.


Mat*_*ock 6

我恰好正在掏出本土的DI代码并用IOC代替它.我可能已经删除了超过200行代码,并与约10是取代它,我必须做学习如何使用容器(南联)的一点点,但我是一个工程师,在互联网技术方面的工作21世纪所以我已经习惯了.我可能花了大约20分钟来看看如何.这非常值得我花时间.

  • "但我是21世纪从事互联网技术的工程师,所以我已经习惯了" - > +1 (3认同)

Aar*_*rch 5

当您继续解耦类并反转依赖关系时,类继续保持较小并且"依赖关系图"的大小继续增长.(这还不错.)使用IoC容器的基本功能可以使所有这些对象的连接变得微不足道,但手动执行此操作会非常繁琐.例如,如果我想创建"Foo"的新实例但需要"Bar",该怎么办?而"Bar"需要"A","B"和"C".而且每个人都需要其他3个等等(是的,我不能想出好的假名:)).

使用IoC容器为您构建对象图可以降低复杂性,并将其推送到一次性配置中.我只是简单地说"给我一个'Foo'",它会找出构建一个所需要的东西.

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

  • 我没有说任何关于安抚的事情.我只是说高级场景可以让新开发人员更加模糊 - 这是事实.我没有说"所以不要这样做".但即使这样(魔鬼的拥护者的时间),通常还有其他方法可以完成同样的事情,使代码库更加明确和平易近人.隐藏您的基础架构工具自定义配置的复杂性只是因为您可能不是正确的理由,并且它吸引了所有人. (4认同)