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

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

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

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


当前回答

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

其他回答

在。net世界里AOP还不是很流行,所以对于依赖注入来说,一个框架是你唯一真正的选择,不管你是自己写一个框架还是使用另一个框架。

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

DI有很多好处,比如减少耦合,从而使单元测试更容易,但是您将如何实现它呢?你想用反射来自己做吗?

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容器配置模块替换了一个工厂实现。

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

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

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

我是声明式编程的爱好者(看看我回答了多少SQL问题),但我所研究的IoC容器似乎太神秘了。

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

...或者两者在某种程度上都是正确的。

我不认为IoC容器的概念不好。但是实现必须足够强大(即灵活),以便在各种各样的应用程序中都有用,同时又要简单易懂。

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

在我看来,您已经构建了自己的IoC容器(使用Martin Fowler描述的各种模式),并且正在询问为什么其他人的实现比您的更好。

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

考虑第三方IoC容器的优点

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

Cons

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

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