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

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

我使用的。net容器包括StructureMapNInject团结Funq。我仍然没有看到IoC容器将如何受益/改进我的代码。

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

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

276052 次浏览

在我看来,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容器采用了一个简单、优雅且有用的概念,并使其成为您需要用200页手册学习两天的东西。

我个人很困惑,IoC社区如何将一个漂亮的Martin Fowler的优雅文章变成一堆复杂的框架,通常有200-300页的手册。

我尽量不去评判(哈哈!),但我认为使用IoC容器的人(A)非常聪明,(B)对不如他们聪明的人缺乏同理心。对他们来说,每件事都很有意义,所以他们很难理解许多普通程序员会感到困惑的概念。它是知识的诅咒。理解IoC容器的人很难相信还有人不理解它。

使用IoC容器最有价值的好处是,您可以在一个地方进行配置切换,从而可以在测试模式和生产模式之间进行更改。例如,假设你有两个版本的数据库访问类…一个版本会频繁地记录日志并进行大量验证,这是您在开发过程中使用的,而另一个版本没有日志记录或验证,但生产速度非常快。能够在一个地方在它们之间切换是很好的。另一方面,这是一个相当微不足道的问题,可以用一种更简单的方式处理,而不需要IoC容器的复杂性。

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

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

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

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

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

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

IoC容器也适用于装入嵌套很深的类依赖项。例如,如果你有以下代码使用依赖注入。

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.
}

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

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

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

  • 通过许多可扩展性点,IoC可用于从配置加载组件。这是一种常见的构建方式,但工具是由容器提供的。

  • 如果您希望将AOP用于一些横切关注点,IoC提供钩子来拦截方法调用。这在项目中不太常见,但IoC让它变得更容易。

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

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

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

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

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

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

对我来说,这听起来像您已经构建了自己的IoC容器(使用Martin Fowler描述的各种模式),并询问为什么别人的实现比你的更好。

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

优点表示考虑第三方IoC容器

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

缺点

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

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

想必没有人强迫您使用依赖注入容器框架。您已经使用DI来解耦类并改进可测试性,因此您将获得许多好处。简而言之,你喜欢简单,这通常是件好事。

如果您的系统达到了一个复杂的水平,手动DI变成了一件苦差事(也就是说,增加了维护),请将其与DI容器框架的团队学习曲线进行权衡。

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

如果使用DI容器,请只使用需要的特性。跳过XML配置文件,在代码中配置即可。坚持构造函数注入。Unity或StructureMap的基本内容可以压缩到几个页面中。

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

当您继续解耦类并反转依赖关系时,类将继续保持较小,而“依赖关系图”的大小将继续增长。(这还不错。)使用IoC容器的基本特性可以使连接所有这些对象变得很简单,但手动操作可能会非常麻烦。例如,如果我想创建一个“Foo”的新实例,但它需要一个“Bar”。一个"Bar"需要一个" a " "B"和"C"每一个都需要另外3个东西,等等(是的,我想不出好的假名字:))。

使用IoC容器为您构建对象图可以大大降低复杂性,并将其推到一次性配置中。我只需说“给我创建一个Foo”,它就会计算出构建一个Foo需要什么。

有些人将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的对象的问题。

这是魔法吗?# EYZ0 !如果您相信这段代码完成了它的工作,那么您就可以安全地跳过所有属性包装的繁文缛节。你有业务问题要解决。

IoC工具用于AOP的其他一些有趣的用法:

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

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

就我个人而言,我使用IoC作为我的应用程序的某种结构图(是的,我也喜欢StructureMap;))。这使得在测试期间用Moq实现代替我通常的接口实现变得很容易。创建一个测试设置可以像对我的ioc -框架进行一个新的初始化调用一样简单,用一个mock代替任何一个测试边界类。

这可能不是IoC存在的目的,但这是我发现自己使用它最多的地方。

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

无论何时使用“new”关键字,您都是在创建一个具体的类依赖,您的头脑中应该敲响警钟。孤立地测试这个物体变得更加困难。解决方案是对接口进行编程并注入依赖项,这样对象就可以用实现该接口的任何东西进行单元测试。模拟)。

问题是你必须在某个地方构造对象。工厂模式是将耦合移出poxo(普通的“在这里插入OO语言”对象)对象的一种方法。如果您和您的同事都像这样编写代码,那么IoC容器就是您可以对代码库进行的下一个“增量改进”。它将把所有讨厌的Factory样板代码从干净的对象和业务逻辑中移出。他们会理解并喜欢它。给公司做个演讲,告诉他们你为什么喜欢这份工作,让每个人都充满热情。

如果你的同事还没有做DI,那么我建议你先专注于DI。宣传如何编写易于测试的干净代码。干净的DI代码是比较困难的部分,一旦做到了这一点,将对象连接逻辑从Factory类转移到IoC容器应该是相对简单的。

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

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

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

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

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

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

IoC框架非常棒,如果你想…

  • ...扔掉类型安全。许多(?)IoC框架迫使您执行代码,如果您想确保所有东西都正确连接。“嘿!希望我已经把一切都设置好了,这样我对这100个类的初始化就不会在生产中失败,抛出空指针异常!”

  • ...在代码中使用全局变量(IoC框架是all关于改变全局状态)。

  • ...编写依赖关系不明确、难以重构的蹩脚代码,因为你永远不知道什么依赖什么。

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,但你真的不想要# eyz2部分,如果你得到了它,它应该是一个警告信号,表明你的设计有问题

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

你不需要需要一个IoC容器。

但是如果您严格遵循依赖注入模式,您会发现使用一个依赖注入模式将会删除大量冗余的、无聊的代码。

无论如何,这通常是使用一个库/框架的最佳时机——当你了解它在做什么并且不需要库也能完成它的时候。

我发现,正确地实现依赖注入往往会迫使程序员使用各种其他编程实践,这些实践有助于提高代码的可测试性、灵活性、可维护性和可伸缩性:实践如单一责任原则、关注点分离和针对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的所有痛苦都会增加。如果应用程序只有一个上下文根,并且从不尝试通过反射实例化服务,那么这就不是一个大问题。然而,任何使用依赖注入的应用程序一旦达到一定规模,管理起来都会变得非常昂贵,除非您使用某种框架来管理依赖关系图。

我正在恢复IOC瘾。我发现现在在大多数情况下很难证明使用IOC进行DI是正确的。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<>注入即可。< / p >

优点:

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

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

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

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

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

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

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

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

有人有什么想法吗?

都快三年了,是吧?

  1. 在支持IoC框架的评论中,有50%的人不理解IoC和IoC框架之间的区别。我怀疑他们是否知道无需部署到应用服务器就可以编写代码

  2. 如果我们以最流行的Java Spring框架为例,IoC配置从XMl转移到代码中,现在看起来是这样的

< p > @ configuration 公共类AppConfig {

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


public @Bean AccountRepository accountRepository() {
return new InMemoryAccountRepository();
}
< p >} ` 我们需要一个框架来做这件事,为什么?< / p >

如果需要集中依赖项的配置,以便轻松地将它们全部交换出去,则需要IoC容器。这在TDD中最有意义,在TDD中,许多依赖项被交换,但依赖项之间几乎没有相互依赖。这样做的代价是在一定程度上混淆了对象构造的控制流,因此拥有一个组织良好且有合理文档的配置是很重要的。有一个这样做的理由也很好,否则,它只是抽象的镀金技术。我见过它做得很糟糕,以至于它被拖到相当于构造函数的goto语句。

在这里就是原因。这个项目被称为IOC-with-Ninject。您可以下载并使用Visual Studio运行它。这个例子使用了Ninject,但是所有的“new”语句都在一个位置,你可以通过改变使用哪个绑定模块来完全改变应用程序的运行方式。示例的设置使您可以绑定到服务的模拟版本或真实版本。在小型项目中,这可能无关紧要,但在大型项目中,这是一件大事。

只是为了澄清一下,我认为他们的优点是: 1) 所有 语句位于代码根的一个位置。 2)只做一个改动就完全重构了代码。 3)“酷因素”加分,因为它……:酷。: p < / p >

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

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

你不需要框架来实现依赖注入。您也可以通过java核心概念来实现这一点。 # EYZ0 < / p >

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

与其他所有东西一样,IOC容器(或者正如爱因斯坦所说的I=OC^2)是一个你必须自己决定是否需要在你的代码中使用它的概念。最近时尚界对国际奥委会的抗议仅仅是时尚。不要追求时尚,这是首要的。有无数的概念可以在代码中实现。首先,自从我开始编程以来,我就一直在使用依赖注入,并在它以这个名称流行时了解了这个术语本身。依赖控制是一个非常古老的主题,到目前为止,它以数万亿种方式被解决,这取决于什么与什么脱钩。将万物与万物分离是无稽之谈。 IOC容器的问题是,它试图像实体框架或NHibernate一样有用。当您必须将任何数据库与您的系统耦合时,编写对象关系映射器是必须的,但IOC容器并不总是必要的。因此,当IOC容器是有用的:

  1. 当你有很多依赖的情况,你想组织
  2. 当你不关心你的代码与第三方产品的耦合时
  3. 当您的开发人员想要学习如何使用新工具时

1:在你的代码中有这么多依赖关系并不常见,或者你在设计的早期就意识到了它们。当需要抽象思维时,抽象思维是有用的。

2:将你的代码与第三方代码耦合是一个巨大的问题。我使用的是10多年前的代码,当时遵循的是ATL、COM、COM+等花哨而先进的概念。现在你对这些代码无能为力了。我想说的是,一个先进的概念会带来明显的优势,但从长远来看,这种优势本身就过时了。这只会让一切都变得更贵。

3:软件开发已经够难了。如果允许一些高级概念裁剪到代码中,则可以将其扩展到无法识别的级别。IOC2有一个问题。尽管它解耦了依赖项,但它也解耦了逻辑流。假设您发现了一个bug,需要设置一个暂停来检查情况。与其他先进的概念一样,IOC2让这变得更加困难。修复概念中的错误要比修复普通代码中的错误困难得多,因为当你修复错误时,必须再次遵守概念。(举个例子,c++ . net的语法不断变化,在重构旧版本的. net之前,你需要好好思考一下。)那么国际奥委会的问题是什么呢?问题在于解决依赖关系。解析的逻辑通常隐藏在IOC2本身中,可能以不常见的方式编写,您需要学习和维护。你的第三方产品会在5年内出现吗?微软则不然。

关于IOC2,“我们知道怎么做”的说法随处可见。这类似于自动化测试。乍一看,这是一个很有趣的术语和完美的解决方案,您只需在晚上执行所有的测试,并在早上看到结果。向一家又一家公司解释自动化测试的真正含义真的很痛苦。自动化测试绝对不是一种快速减少bug数量的方法,而您可以在一夜之间引入这些bug来提高产品质量。但是,时尚正在让这种观念烦人地占据主导地位。IOC2也有同样的症状。人们相信,为了使您的软件更好,您需要实现它。最近的每一次采访,我都被问到是否正在实现IOC2和自动化。这是一种时尚的标志:该公司有一些用MFC编写的代码,他们不会放弃。

你需要像学习软件中的其他概念一样学习IOC2。是否需要使用IOC2是由团队和公司内部决定的。但是,在做出决定之前,至少必须提到以上所有的理由。只有当你看到积极的一面超过消极的一面,你才能做出积极的决定。

IOC2没有什么错,除了它只解决它解决的问题,并引入它引入的问题。什么都没有。然而,逆潮流而动是很困难的,他们有汗嘴,什么都有追随者。奇怪的是,当他们的幻想的问题变得明显时,他们一个也没有。 软件行业的许多概念都得到了维护,因为它们创造了利润,写了书,召开了会议,制造了新产品。这就是时尚,通常是短暂的。一旦人们找到了别的东西,他们就会完全放弃它。IOC2是有用的,但它显示出与我见过的许多其他消失的概念相同的迹象。我不知道它能否存活下来。这是没有规则的。你认为如果它有用,它就会存在。不,不是那样的。一家有钱的大公司就足够了,这个概念可能在几周内就会消亡。我们将会看到。NHibernate幸存了下来,EF屈居第二。也许IOC2也会幸存下来。 不要忘记,软件开发中的大多数概念并没有什么特别之处,它们非常符合逻辑,简单而明显,有时记住当前的命名约定比理解概念本身更难。掌握IOC2知识能让开发者成为更好的开发者吗?不,因为如果一个开发人员不能提出一个本质上类似于IOC2的概念,那么他或她将很难理解IOC2正在解决的问题,使用它将看起来是人为的,他或她可能会为了某种政治正确而开始使用它

我知道这是一个相当老的帖子,但它似乎仍然相当活跃,我想我可以贡献一些在其他回答中没有提到的观点。

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

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

2) IoC容器,在我看来,是一种“黑盒编程”。我特别发现,我们缺乏经验的开发人员倾向于滥用它们。它允许程序员不必考虑对象之间应该如何相互关联或如何解耦它们,因为它为他们提供了一种机制,在这种机制中,他们可以简单地从稀薄的空气中获取任何他们想要的对象。例如,可能有一个很好的设计理由,ObjectA永远不应该直接知道ObjectB,但是一个没有经验的程序员会简单地说“没问题,我只是从IoC容器中获取ObjectB”,而不是创建一个工厂或桥接器或服务定位器。这实际上会导致增加对象耦合,这正是IoC应该帮助防止的。