关于依赖注入:为什么我需要一个IOC容器而不是直接的DI代码?

Why do I need an IoC container as opposed to straightforward DI code?

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

我玩过.NET容器,如structuremap、ninject、unity和funq。我仍然看不到IOC容器将如何帮助/改进我的代码。

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

请让我相信我需要使用一个IOC容器。当我和同事们交谈时,我将使用这些论点。


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

1
2
3
4
var svc = new ShippingService(new ProductLocator(),
   new PricingService(), new InventoryService(),
   new TrackingRepository(new ConfigProvider()),
   new Logger(new EmailLogger(new ConfigProvider())));

在此之上:

1
var svc = IoC.Resolve<IShippingService>();

许多人不知道依赖链可以嵌套,手动连接它们很快就会变得不方便。即使有了工厂,代码的复制也不值得。

是的,IOC容器可能很复杂。但对于这个简单的案例,我已经证明了这是非常简单的。

好吧,让我们进一步证明这一点。假设您有一些要绑定到智能用户界面的实体或模型对象。这个智能用户界面(我们称之为Shindows Morms)希望您实现inotifyPropertiesChanged,以便它可以进行更改跟踪并相应地更新用户界面。

"好吧,听起来不那么难",所以你开始写作。

从以下内容开始:

1
2
3
4
5
6
7
public class Customer
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime CustomerSince { get; set; }
    public string Status { get; set; }
}

…最后是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
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工具,您可以执行如下操作:

1
var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());

噗!现在,在所讨论对象的每个虚拟属性设置器上,所有这些手册inotifypropertychanged bs都会自动为您生成。

这是魔法吗?对!如果您可以相信这段代码完成了它的工作,那么您就可以安全地跳过包装mumbo-jumbo的所有属性。你有商业问题要解决。

IOC工具在AOP中的其他一些有趣用途:

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


我和你在一起,瓦迪姆。IOC容器采用一个简单、优雅、有用的概念,用一本200页的手册让你学习两天。

我个人对国际奥委会如何将马丁·福勒的一篇优美的文章变成一堆复杂的框架感到困惑,这些框架通常有200-300页的手册。

我尽量不作判断(哈哈!)但我认为,使用IOC容器的人(a)非常聪明,(b)缺乏对那些不如他们聪明的人的同情心。每件事对他们来说都很有意义,所以他们很难理解许多普通的程序员会发现概念混淆。这是知识的诅咒。了解IOC容器的人很难相信有人不了解它。

使用IOC容器最有价值的好处是,您可以在一个地方有一个配置开关,允许您在测试模式和生产模式之间进行更改。例如,假设您有两个版本的数据库访问类…一个版本的日志记录非常活跃,并进行了大量的验证,这是您在开发过程中使用的,另一个版本没有日志记录或验证,生产速度非常快。能在一个地方在它们之间切换是很好的。另一方面,这是一个非常小的问题,可以用一种简单的方式处理,而不必考虑IOC容器的复杂性。

我相信,如果您使用IOC容器,坦率地说,您的代码将变得很难阅读。为了弄清楚代码要做什么,您必须查看的地方至少增加了一个。在天堂的某个地方,一位天使在呼喊。


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

如果您的系统达到了一个复杂的水平,而手工DI变成了一项繁琐的工作(即,增加维护),那么将其与DI容器框架的团队学习曲线进行权衡。

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

如果您使用DI容器,那么只使用您需要的特性。如果足够的话,跳过XML配置文件并用代码配置它。坚持构造函数注入。统一或结构图的基础可以压缩成几页。

MarkSeemann在这方面发表了一篇很棒的博客文章:何时使用DI容器


IOC容器也适合加载深度嵌套的类依赖项。例如,如果您使用Depedency注入具有以下代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
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,并且所有子依赖项都将自动得到解析。

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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.
}


在我看来,IOC最大的好处是能够集中配置您的依赖项。

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

1
2
3
4
5
6
7
8
9
10
11
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类,而不是更混乱的配置文件,那么您可以使用类似这样的文件:

1
2
3
4
5
6
7
8
9
10
11
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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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.
   }
}


我是声明式编程的爱好者(看看我回答了多少SQL问题),但是我看到的IOC容器对于它们自己来说太神秘了。

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

…或者两者在某种程度上都是真的。

我认为IOC容器的概念并不坏。但是,实现必须既强大(也就是说灵活),足以在各种应用程序中发挥作用,又简单易懂。

可能是一打半打中的六个。一个真正的应用程序(不是玩具或演示)必然是复杂的,会导致许多角落案例和规则例外。要么用命令式代码封装这种复杂性,要么用声明性代码封装。但你必须在某个地方代表它。


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

  • 减少发球主程序的启动程序。
  • 实现相当深入的部署时间重新配置功能。
  • 使依赖注入式成为新工作阻力最小的途径。

当然,可能会有困难:

  • 需要复杂的启动/关闭/生命周期管理的代码可能不容易适应容器。
  • 您可能需要处理任何个人、流程和团队文化问题——但是,这就是为什么您要求……
  • 一些工具包本身也很快变得很重,这鼓励了许多DI容器开始作为一种对抗的深度依赖。

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

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

考虑第三方国际奥委会集装箱的专家

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

欺骗

  • 你可以免费得到错误介绍:)
  • 图书馆的设计可能比你的差
  • 你必须学习一个新的API
  • 您永远不会使用的功能太多
  • 通常很难调试您没有编写的代码
  • 从以前的IOC容器迁移可能很麻烦

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


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

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

  • 对于多租户,IOC容器可以处理一些用于加载不同客户机资源的基础结构代码。当您需要一个特定于客户机的组件时,可以使用自定义选择器来处理逻辑,而不必担心来自客户机代码的逻辑。你当然可以自己建造,但这里有一个国际奥委会可以帮助的例子。

  • 有了许多扩展点,IOC可以用来从配置中加载组件。这是一种常见的构建方法,但是工具是由容器提供的。

  • 如果您想使用AOP解决一些横切问题,IOC提供了钩子来拦截方法调用。这是不常见的项目特设,但国际奥委会使它更容易。

我以前写过这样的功能,但是如果我现在需要这些功能中的任何一个,如果它适合我的体系结构,我宁愿使用预构建和测试的工具。

正如其他人提到的,您还可以集中配置要使用的类。虽然这可能是件好事,但它是以错误的方向和复杂性为代价的。大多数应用程序的核心组件没有被替换太多,因此要进行权衡比较困难。

我使用一个IOC容器并欣赏它的功能,但必须承认我已经注意到了这一点:我的代码在类级别上变得更清晰,在应用程序级别(即可视化控制流)变得不那么清晰。


我是一个正在康复的国际奥委会瘾君子。我发现现在大多数情况下使用IOC进行DI很难证明这一点。IOC容器牺牲了编译时检查,而且作为回报,它可以提供"简单"的设置、复杂的生命周期管理以及在运行时即时发现依赖项。我发现,在大多数情况下,编译时检查的丢失以及由此产生的运行时魔力/异常不值得大吹大擂。在大型企业应用程序中,它们会使跟踪正在发生的事情变得非常困难。

我不赞成使用集中化参数,因为您也可以非常容易地集中静态设置,方法是为应用程序使用一个抽象工厂,并将对象创建严格地推迟到抽象工厂,即执行适当的DI。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
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容器配置模块替换工厂实现。


如果你想…

  • …丢弃式安全。许多(所有?)IOC框架强制您执行代码,如果您想确保所有内容都正确连接。"嘿!希望我已设置好所有内容,以便这100个类的初始化不会在生产中失败,从而引发空指针异常!"

  • …把你的代码乱扔到全球(国际奥委会框架都是关于改变全球国家的)。

  • …编写带有不清晰依赖项的糟糕代码,这些依赖项很难重构,因为您永远不知道什么依赖于什么。

国际奥委会的问题是,使用他们的人曾经写过这样的代码

1
2
3
4
5
6
public class Foo {
    public Bar Apa {get;set;}
    Foo() {
        Apa = new Bar();
    }
}

这显然有缺陷,因为foo和bar之间的依赖是硬连接的。然后他们意识到写这样的代码会更好

1
2
3
4
5
6
public class Foo {
    public IBar Apa {get;set;}
    Foo() {
        Apa = IoC<IBar>();
    }
}

这也是有缺陷的,但不太明显。在haskell中,Foo()的类型应该是IO Foo,但是你真的不想要IO的部分,如果你得到它,它应该是一个警告信号,表明你的设计出了问题。

为了摆脱它(IO部分),获得IOC框架的所有优点,并且没有任何缺点,您可以使用抽象工厂。

正确的解决方案应该是

1
data Foo = Foo { apa :: Bar }

或者也许

1
data Foo = forall b. (IBar b) => Foo { apa :: b }

注入(但我不会称之为注入)酒吧。

还有:看看这段视频和Erik Meijer(Linq的发明者)在一起,他说Di是为那些不懂数学的人(我不能同意更多的观点):http://www.youtube.com/watch?V= 8MTTJYF-8P4

与斯波斯基不同的是,我不相信使用IOC框架的人非常聪明——我只是相信他们不懂数学。


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

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

如果不使用IOC容器,我必须在需要它的对象实际使用之前,通过2、3、…、n个对象传递设置和/或元数据。

DI/IOC容器还有许多其他的好处,正如其他人在这里详细描述的,从创建对象的想法到请求对象的过程可能会让人心烦意乱,但是使用DI对我和我的团队非常有帮助,所以也许您可以将它添加到您的兵工厂中!


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

但它也倾向于创建相当大的依赖树,通过框架(尤其是使用约定)而不是手工管理依赖树要容易得多。今天我想在linqpad中快速测试一些东西,我觉得在我的模块中创建内核和加载会很麻烦,最后我手工编写了这个:

1
2
3
4
5
6
7
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试图通过对每个请求的反射来实例化控制器。要手动进行依赖项注入,您必须使每个控制器成为"上下文根",如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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框架为您进行比较:

1
2
3
4
5
6
7
8
9
public MyController : Controller
{
    private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
    public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
    {
        _simpleMerger = simpleMerger;
    }
    ...
}

使用DI框架时,请注意:

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

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


每当您使用"new"关键字时,都会创建一个具体的类依赖关系,并且会在您的头脑中发出一个小小的警报。孤立地测试这个对象变得更加困难。解决方案是编程到接口并注入依赖项,以便对象可以用实现该接口的任何东西(例如mocks)进行单元测试。

问题是你必须在某个地方构建对象。工厂模式是将耦合从poxos(简单的"在这里插入OO语言"对象)中移出的一种方法。如果您和您的同事都在编写这样的代码,那么IOC容器是您可以对代码库进行的下一个"增量改进"。它将把所有讨厌的工厂样板代码从干净的对象和业务逻辑中移出。他们会得到并喜欢的。见鬼,跟公司谈谈你为什么喜欢它,让每个人都兴奋起来。

如果你的同事还没有进行DI,那么我建议你先关注一下。宣传如何编写易于测试的干净代码。干净的DI代码是最难的部分,一旦你到了那里,将对象连接逻辑从工厂类转移到IOC容器应该是相对简单的。


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


你不需要一个IOC容器。

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

不管怎样,这通常是使用库/框架的最佳时机——当您了解它在做什么并且可以在没有库的情况下完成它时。


我只是碰巧在取消国产DI代码并用IOC替换它的过程中。我可能已经删除了超过200行代码,并用大约10行代码替换了它。是的,我必须学习一下如何使用容器(winsor),但是我是一个在21世纪从事互联网技术的工程师,所以我已经习惯了。我大概花了20分钟的时间来研究如何操作。这很值得我花时间。


当您继续分离类并反转依赖关系时,类继续保持较小,"依赖关系图"继续增大。(这还不错。)使用IOC容器的基本特性使得连接所有这些对象变得很简单,但是手动操作会非常麻烦。例如,如果我想创建一个"foo"的新实例,但它需要一个"bar",该怎么办?"酒吧"需要"A"、"B"和"C"。每个人都需要3件其他的东西,等等(是的,我不能想出好的假名字)。

使用IOC容器为您构建对象图可以大大降低复杂性,并将其推出到一次性配置中。我只是简单地说"给我创造一个‘foo’",它就知道了构建一个foo需要什么。

有些人将IOC容器用于更多的基础设施,这对于高级场景来说是很好的,但是在这些情况下,我同意它可以使新开发人员的代码变得模糊,并且很难读取和调试。


关于统一的同上。太大了,你就能听到椽子吱吱作响的声音。

当人们开始滔滔不绝地谈论国际奥委会代码看起来是同一类的人时,我从未感到惊讶,他们曾经谈论过C++中的模板是如何在90年中回溯到优雅的方式的,然而现在,他们将谴责它们是晦涩难懂的。呸!


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

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

DI有许多好处,比如减少耦合,这样单元测试就更容易了,但是您将如何实现它呢?你想用反省自己来做吗?


已经快三年了,是吗?

  • 50%赞成国际奥委会框架的人不理解国际奥委会框架和国际奥委会框架之间的区别。我怀疑他们知道你可以在不部署到应用服务器的情况下编写代码

  • 如果我们采用最流行的Java Spring框架,IoC配置从XML移动到代码中,现在看起来像这样

  • @配置公共类AppConfig{

    1
    2
    3
    4
    5
    6
    7
    public @Bean TransferService transferService() {
        return new TransferServiceImpl(accountRepository());
    }

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

    }`我们需要一个框架来做到这一点为什么?


    老实说,我不认为有很多情况下需要IOC容器,而且大多数情况下,它们只是增加了不必要的复杂性。

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

    如果是,那么您可能需要一个IOC容器。如果没有,那么您只是将初始化从开发人员容易看到的地方移开。

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

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

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

    有人对此有什么想法吗?


    如果需要集中化依赖项的配置,那么您将需要一个IOC容器,这样就可以方便地大规模交换它们。在TDD中,这是最有意义的,在TDD中,许多依赖项被交换出去,但是依赖项之间的依赖性很小。这样做的代价是在一定程度上混淆了对象构造的控制流,因此拥有一个组织良好且合理记录的配置是很重要的。有理由这样做也很好,否则,它只是抽象镀金。我看到它做得如此糟糕,以至于它被拖到相当于构造函数的goto语句。


    这就是原因。该项目被称为国际奥委会与尼尼特。您可以下载并使用Visual Studio运行它。此示例使用ninject,但所有"new"语句都在一个位置,您可以通过更改要使用的绑定模块来完全更改应用程序的运行方式。该示例是这样设置的,这样您就可以绑定到服务的模拟版本或真实版本。在可能无关紧要的小项目中,但在大项目中,这是一件大事。

    只是说清楚,我看到的优势是:1)所有新语句位于代码根的一个位置。2)完全重新考虑了一次变更后的代码。3)"酷因子"额外得分,因为它…嗯,很酷。P


    我将试着从我的角度找出为什么国际奥委会可能不好。好的。

    和其他所有东西一样,i oc容器(或者爱因斯坦所说的i=oc^2)是一个概念,您必须自己决定是否需要它在您的代码中。最近对国际奥委会的强烈抗议只是时尚。不要迷恋时尚,这是第一步。您可以在代码中实现无数的概念。首先,自从我开始编程以来,我就在使用依赖注入,并在这个名称下普及时学习了这个术语本身。依赖性控制是一个非常古老的主题,到目前为止,它是以数万亿种方式解决的,这取决于什么是与什么脱钩的。把每件事都和每件事脱钩是胡说八道。IOC容器的问题在于它试图像实体框架或nhibernate一样有用。虽然编写对象关系映射器只是必须的,但只要您必须将任何数据库与您的系统结合起来,IOC容器并不总是必要的。所以当IOC容器有用时:好的。

  • 当您有许多依赖关系时,您希望组织
  • 当您不关心将代码与第三方产品耦合时
  • 当开发人员想学习如何使用新工具时
  • 1:代码中有这么多依赖项,或者在设计早期就知道它们,这并不是经常发生的。当抽象思维到期时,抽象思维是有用的。好的。

    2:将代码与第三方代码耦合是一个巨大的问题。我当时正在处理10岁以上的代码,当时遵循的是奇特的高级概念ATL、COM、COM+等等。现在您对该代码无能为力。我要说的是,一个先进的概念给了一个明显的优势,但从长远来看,这是取消了与过时的优势本身。只是让它变得更贵了。好的。

    3:软件开发已经够难了。如果您允许一些高级概念出现在代码中,那么可以将其扩展到不可识别的级别。IOC2有问题。虽然它是分离依赖项,但它也分离逻辑流。假设您发现了一个bug,需要设置一个中断来检查情况。IOC2,和其他任何先进的概念一样,正使得这变得更加困难。在一个概念中修复一个bug比在更简单的代码中修复一个bug更困难,因为当您修复一个bug时,必须再次遵循一个概念。(只是给你一个例子,C++.NET经常改变语法,以至于你需要在重构旧版本的.NET之前仔细思考),那么IOC的问题是什么?解决依赖项时出现问题。解决的逻辑通常隐藏在ioc2本身中,以您需要学习和维护的不常见方式编写。你的第三方产品5年后会出现吗?微软没有。好的。

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

    您需要学习IOC2作为软件中的任何其他概念。如果需要使用IOC2,则由团队和公司决定。但是,在作出决定之前,至少必须提及上述所有论据。只有当你看到积极的一面大于消极的一面,你才能做出积极的决定。好的。

    IOC2没有什么问题,只是它只解决了它解决的问题,并引入了它引入的问题。没有别的了。然而,与时尚背道而驰是非常困难的,他们有着汗流满面的嘴巴,什么都有追随者。奇怪的是,当他们狂热的问题变得明显时,他们中的任何一个都不存在。软件工业中的许多概念都受到了捍卫,因为它们创造利润、编写书籍、召开会议、制造新产品。这是时尚,通常是短暂的。一旦人们发现了其他的东西,他们就会完全放弃它。ioc2是有用的,但它显示的符号和我见过的许多其他消失的概念相同。我不知道它是否能存活。这是没有规则的。你认为如果它有用的话,它会生存下来。不,不是那样的。一家大的富裕公司就足够了,这个概念在几周内就会消失。我们拭目以待。NHibernate存活下来,EF排名第二。或许IOC2也能存活下来。不要忘记,软件开发中的大多数概念都没有什么特别的,它们非常合乎逻辑、简单和明显,有时记住当前的命名约定比理解概念本身更困难。IOC2的知识是否使开发人员成为更好的开发人员?不,因为如果开发人员不能提出类似于ioc2的概念,那么他或她将很难理解ioc2正在解决的问题,使用它将看起来是人造的,他或她可能会为了某种政治正确而开始使用它。好的。好啊。


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

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

    1)让第三方库"自动"管理对象的生命周期可以带来意想不到的结果。我们发现,特别是在大型项目中,一个对象的副本可能比您预期的要多得多,如果您手动管理生命周期的话,副本可能比您预期的要多。我相信,这取决于所使用的框架,但问题仍然存在。如果您的对象拥有资源、数据连接等,这也可能是有问题的,因为对象有时可能比您期望的寿命长。因此,IOC容器不可避免地会增加应用程序的资源利用率和内存占用。

    2)我认为IOC容器是"黑盒编程"的一种形式。我发现,特别是我们经验不足的开发人员往往滥用它们。它允许程序员不必考虑对象应该如何相互关联,或者如何将它们解耦,因为它为他们提供了一种机制,在这种机制中,他们可以简单地从稀薄的空气中抓取任何他们想要的对象。例如,可能有一个很好的设计原因,即Objecta永远不应该直接了解ObjectB,但一个没有经验的程序员会简单地说:"没问题,我只是从IOC容器中获取ObjectB",而不是创建工厂、桥梁或服务定位器。这实际上会导致对象耦合的增加,这是IOC应该帮助防止的。


    IOC容器解决了一个您可能没有的问题,但这是一个很好的问题

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


    您不需要框架来实现依赖注入。您也可以通过核心Java概念来实现这一点。http://en.wikipedia.org/wiki/dependency_injection_code_instruction_使用_java


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

    这可能不是国际奥委会的目的,但我发现我自己最常使用它。


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