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

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

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

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


当前回答

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

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

其他回答

在我看来,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.
   }
}

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框架的人很聪明——我只是认为他们不懂数学。

I'm a recovering IOC addict. I'm finding it hard to justify using IOC for DI in most cases these days. IOC containers sacrifice compile time checking and supposedly in return give you "easy" setup, complex lifetime management and on the fly discovering of dependencies at run time. I find the loss of compile time checking and resulting run time magic/exceptions, is not worth the bells and whistles in the vast majority of cases. In large enterprise applications they can make it very difficult to follow what is going on.

我不相信集中化的说法,因为你可以通过为你的应用程序使用一个抽象工厂,并虔诚地将对象创建推迟到抽象工厂,即进行适当的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<>注入即可。

优点:

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

我建议持怀疑态度的人尝试下一个新项目,诚实地问问自己什么时候需要这种容器。稍后很容易引入IOC容器,因为您只是用IOC容器配置模块替换了一个工厂实现。

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

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