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

时间:2009-05-16 01:06:18

标签: dependency-injection inversion-of-control ioc-container

我一直在使用Dependency Injection(DI)一段时间,在构造函数,属性或方法中注入。我从未觉得需要使用Inversion of Control(IoC)容器。但是,我读的越多,我觉得社区使用IoC容器的压力就越大。

我使用的是StructureMapNInjectUnityFunq等.NET容器。我仍然没有看到IoC容器如何使我的代码受益/改进。

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

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

30 个答案:

答案 0 :(得分:441)

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

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的实体或模型对象。这个智能用户界面(我们称之为Shindows Morms)希望您实现INotifyPropertyChanged,以便它可以进行更改跟踪&amp;相应地更新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());

Poof!所有手册INotifyPropertyChanged BS现在都会自动为您生成,位于相关对象的每个虚拟属性设置器上。

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

IoC工具用于执行AOP的其他一些有趣用途:

  • 陈述性&amp;嵌套数据库事务
  • 陈述性&amp;嵌套工作单元
  • 登录
  • 前/后条件(按合同设计)

答案 1 :(得分:138)

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

我个人很困惑IoC社区如何使用漂亮的elegant article by Martin Fowler并将其变成一堆复杂的框架,通常有200-300页的手册。

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

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

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

答案 2 :(得分:37)

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

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

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

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

Mark Seemann撰写了一篇很棒的博客文章:When to use a DI Container

答案 3 :(得分: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
}

如果您使用静态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
}

然后,您的静态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.
   }
}

答案 4 :(得分: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 { }

如果您已将所有这些依赖项加载到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.
}

答案 5 :(得分:28)

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

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

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

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

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

答案 6 :(得分:27)

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

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

当然,可能有困难:

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

答案 7 :(得分:23)

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

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

用于考虑第三方IoC容器的

优点

  • 你得到免费修复的错误
  • 图书馆设计可能比你的更好
  • 人们可能已经熟悉特定的图书馆
  • 图书馆可能比你的快
  • 它可能有一些您希望实现的功能,但从未有时间(您是否有服务定位器?)

<强>缺点

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

所以,权衡你的利弊与你的利弊并作出决定。

答案 8 :(得分:17)

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

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

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

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

  • 如果您想使用AOP来解决某些横切问题,请IoC provides hooks拦截方法调用。这在项目上不太常见,但IoC使其更容易。

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

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

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

答案 9 :(得分:16)

我是一名康复的IOC瘾君子。这些天我发现很难证明在大多数情况下使用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();
        }
    }
}

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

优点:

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

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

答案 10 :(得分:14)

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 }

并注入(但我不会称之为注入)Bar。

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

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

答案 11 :(得分:14)

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

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

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

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

答案 12 :(得分:10)

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

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

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

回想起来,使用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())
    }
    ...
}

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

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

使用DI框架,请注意:

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

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

答案 13 :(得分:9)

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

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

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

答案 14 :(得分:8)

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

答案 15 :(得分:7)

不需要 IoC容器。

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

这通常是使用库/框架的最佳时机 - 无论如何 - 当你理解它正在做什么时,可以在没有库的情况下完成它。

答案 16 :(得分:6)

我恰好正在掏出本土的DI代码并用IOC取而代之。我可能删除了超过200行代码并用大约10代替。是的,我不得不做一些关于如何使用容器(Winsor)的知识,但我是一名从事互联网技术工作的工程师。 21世纪所以我已经习惯了。我可能花了大约20分钟来看看如何。这非常值得我花时间。

答案 17 :(得分:5)

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

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

有些人将IoC容器用于更多的基础架构,这对于高级场景来说很好,但在这些情况下我同意它可能会使新的开发人员难以进行模糊处理并使代码难以阅读和调试。

答案 18 :(得分:4)

关于Unity的Dittos。太大了,你可以听到椽子里的吱吱声。

当人们开始大肆宣传IoC代码看起来如何干净时,我从来不会感到惊讶的是同样的人们曾经一度谈到C ++中的模板是如何在90年代回归的优雅方式,但现在却会谴责他们像奥术一样。呸!

答案 19 :(得分:2)

在.NET世界中,AOP并不太受欢迎,因此对于DI来说,框架是您唯一真正的选择,无论您是自己编写还是使用其他框架。

如果您使用AOP,则可以在编译应用程序时注入,这在Java中更为常见。

DI有许多好处,例如减少耦合,因此单元测试更容易,但您将如何实现它?你想用反射来自己做吗?

答案 20 :(得分:2)

所以,已经差不多3年了,是吗?

  1. 50%赞成IoC框架的人不了解IoC和IoC框架之间的区别。我怀疑他们知道您可以在不部署到应用服务器的情况下编写代码

  2. 如果我们采用最流行的Java Spring框架,那么IoC配置从XMl迁移到代码中,现在看起来像这样

  3. `@Configuration 公共类AppConfig {

    public @Bean TransferService transferService() {
        return new TransferServiceImpl(accountRepository());
    }
    
    public @Bean AccountRepository accountRepository() {
        return new InMemoryAccountRepository();
    }
    

    } ` 我们需要一个框架才能完全解决这个问题?

答案 21 :(得分:1)

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

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

如果是,那么您可能需要一个IoC容器。如果没有,那么您只是将初始化从开发人员可以轻松看到的位置移开。

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

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

首先,首先要设置容器。然后,您仍然需要定义需要初始化的每个对象。所以你不要在初始化中保存代码,你移动它(除非你的对象被多次使用。作为Singleton它更好吗?)。然后,对于以这种方式初始化的每个对象,您必须创建并维护一个接口。

有人对此有任何想法吗?

答案 22 :(得分:1)

如果您需要集中依赖项的配置,那么您将需要一个IoC容器,以便它们可以轻松地交换出来。这在TDD中最有意义,其中许多依赖项被换出,但依赖关系之间几乎没有相互依赖性。这样做的代价是在某种程度上混淆了对象构造的控制流程,因此具有良好组织和合理记录的配置是重要的。有理由这样做也很好,否则,它只是abstraction gold-plating。我已经看到它做得很糟糕,以至于它被拖到了构造函数的goto语句中。

答案 23 :(得分:1)

Here就是为什么。该项目名为IOC-with-Ninject。您可以使用Visual Studio下载并运行它。此示例使用Ninject,但所有“新”语句都在一个位置,您可以通过更改要使用的绑定模块来完全更改应用程序的运行方式。设置该示例,以便您可以绑定到服务的模拟版本或实际版本。在可能无关紧要的小项目中,但在大型项目中,这是一个大问题。

为了清楚起见,我看到它们的优势: 1)所有 语句位于代码根目录的一个位置。 2)通过一次更改完全重新计算代码。 3)“酷因子”的额外分数因为......好吧:很酷。 :P

答案 24 :(得分:1)

我将试图从我的角度找出为什么IOC可能不好。

与其他所有内容一样,IOC容器(或爱因斯坦会将其设为I = OC ^ 2)是您必须自己决定的概念,如果您在代码中需要或不需要它。最近时尚界对国际奥委会的强烈抗议只是时尚。不要因为时尚而堕落,这是第一次。您可以在代码中实现无数的概念。首先,我使用依赖注入,因为我已经开始编程,并且当它在该名称下推广时学习了这个术语。依赖控制是一个非常古老的主题,到目前为止,它以数万种方式得到了解决,具体取决于与什么分离的东西。将一切与一切分离是一种废话。 IOC容器的问题在于它试图像Entity Framework或NHibernate一样有用。虽然只要必须将任何数据库与系统耦合,编写对象关系映射器就是必须的,但IOC容器并不总是必需的。所以当IOC容器很有用时:

  1. 如果您想要组织许多依赖项的情况
  2. 如果您不关心将代码与第三方产品结合使用
  3. 当您的开发人员想要学习如何使用新工具时
  4. 1:您的代码中依赖于这么多依赖项,或者您在设计早期就意识到它们并不常见。抽象思维到期时,抽象思维很有用。

    2:将代码与第三方代码耦合是一个HuGe问题。我正在处理10年以上的代码,当时正在关注ATL,COM,COM +等高级概念。现在你无法使用该代码。我所说的是,先进的概念具有明显的优势,但从长远来看,这已经取消了过时的优势本身。它刚刚使所有这些都变得更加昂贵。

    3:软件开发很难。如果您允许某些高级概念进入代码,则可以将其扩展到无法识别的级别。 IOC2存在问题。虽然它正在解耦依赖关系,但它也解耦了逻辑流程。想象一下,你发现了一个错误,你需要设置一个休息来检查情况。与任何其他先进概念一样,IOC2正在使其变得更加困难。修复概念中的错误比修复普通代码中的错误更困难,因为当您修复错误时,必须再次遵守概念。 (仅举一个例子,C ++ .NET不断改变语法,以至于在重构某些旧版本的.NET之前需要仔细思考。)那么IOC的问题是什么?问题在于解决依赖关系。解决的逻辑通常隐藏在IOC2本身中,可能以非常常见的方式编写,您需要学习和维护。您的第三方产品会在5年后出现吗?微软不是。

    &#34;我们知道&#34;关于IOC2的所有地方都写有综合症。这类似于自动化测试。乍一看,花哨的术语和完美的解决方案,您只需将所有测试执行过夜,并在早上查看结果。向公司解释自动化测试的真正含义是非常痛苦的。自动化测试绝对不是减少一夜之间可以引入的错误数量的快速方法,以提高产品质量。但是,时尚正在使这种观念成为令人讨厌的主导。 IOC2患有同样的综合症。相信您需要实施它才能使您的软件变得更好。 EvErY最近的采访我被问到我是否正在实施IOC2和自动化。这是时尚的标志:公司有部分代码用MFC编写,他们不会放弃。

    您需要像软件中的任何其他概念一样学习IOC2。是否需要使用IOC2的决定是在团队和公司内部。但是,在作出决定之前,必须至少提及以上所有论点。只有当你看到正面胜过负面时,你才能做出积极的决定。

    IOC2没有任何问题,只是它确实解决了它解决的问题并引入了它引入的问题。没有其他的。然而,反对时尚是非常困难的,他们有汗口,任何事情的追随者。奇怪的是,当他们的幻想问题变得明显时,他们中的任何一个都没有。 软件行业中的许多概念都得到了捍卫,因为它们创造了利润,书籍编写,会议召开,新产品制作。这是时尚,通常是短暂的。一旦人们发现其他东西,他们就会完全抛弃它。 IOC2很有用,但它显示出与我见过的许多其他消失概念相同的符号。我不知道它是否会存活下来。没有规则。你认为如果它有用,它就会存活下来。不,它不是那样的。一个大的富裕公司就足够了,这个概念可以在几周内消亡。我们会看到。 NHibernate幸免于难,EF排名第二。也许IOC2也会存活下来。 不要忘记,软件开发中的大多数概念都没有什么特别之处,它们非常合乎逻辑,简单明了,有时候记住当前的命名约定要比理解概念本身更难。 IOC2的知识是否使开发人员成为更好的开发人员?不,因为如果开发人员无法提出与IOC2性质相似的概念,那么他或她很难理解IOC2正在解决哪个问题,使用它会看起来很人为,他或她可能会开始使用它为了某种政治上的正确。

答案 25 :(得分:0)

就个人而言,我使用IoC作为我的应用程序的某种结构图(是的,我也更喜欢StructureMap;))。它使得在测试期间用Moq实现替换我的ussual接口实现变得容易。创建测试设置可以像对IoC框架进行新的init调用一样简单,用模拟替换我的测试边界中的任何一个类。

这可能不是IoC的用途,但这是我发现自己最常使用的..

答案 26 :(得分:0)

国际奥委会集装箱解决了你可能没有的问题,但这是一个很好的问题

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

答案 27 :(得分:0)

您不需要框架来实现依赖注入。您也可以通过核心Java概念来实现。 http://en.wikipedia.org/wiki/Dependency_injection#Code_illustration_using_Java

答案 28 :(得分:0)

我意识到这是一个相当古老的帖子,但似乎仍然相当活跃,我认为我会贡献一些尚未在其他答案中提及的观点。

我会说我同意依赖注入的好处,但我更喜欢自己构建和管理对象,使用的模式与Maxm007在其答案中概述的模式不同。我发现使用第三方容器存在两个主要问题:

1)让第三方库管理对象的生命周期&#34;自动化&#34;可以出乎意料的结果。我们发现,特别是在大型项目中,您可以拥有比预期更多的对象副本,并且比手动管理生命周期更多。我确定这取决于所使用的框架,但问题仍然存在。如果您的对象包含资源,数据连接等,这也可能会有问题,因为对象有时可能比您预期的更长寿。因此,IoC容器不可避免地会增加应用程序的资源利用率和内存占用量。

2)在我看来,IoC容器是&#34;黑盒编程的一种形式&#34;。我发现,特别是我们经验不足的开发人员倾向于滥用它们。它允许程序员不必考虑对象应该如何相互关联或如何将它们分离,因为它为它们提供了一种机制,在这种机制中,它们可以毫无顾忌地抓住他们想要的任何对象。例如,可能有一个很好的设计理由,即ObjectA不应该直接了解ObjectB,而不是创建工厂或桥接器或服务定位器,没有经验的程序员只会说“没问题,我只是抓住”来自IoC容器的ObjectB&#34;。这实际上可以导致增加对象耦合,这是IoC应该帮助防止的。

答案 29 :(得分:-8)

ASP.NET项目中的依赖注入可以通过几行代码完成。我认为当你有一个使用多个前端并需要单元测试的应用程序时,使用容器有一些优势。