我使用依赖注入(DI)已有一段时间了,注入了构造函数,属性或方法。我从来没有觉得需要使用控制反转(IoC)容器。但是,我阅读的内容越多,社区使用IoC容器的压力就越大。
我玩过.NET容器,如StructureMap,NInject,Unity和Funq。我仍然看不到IoC容器如何使我的代码受益/改进。
我也害怕在工作中开始使用容器,因为许多同事会看到他们不理解的代码。他们中的许多人可能不愿意学习新技术。
请说服我,我需要使用IoC容器。与工作中的其他开发人员交谈时,我将使用这些参数。
我使用依赖注入(DI)已有一段时间了,注入了构造函数,属性或方法。我从来没有觉得需要使用控制反转(IoC)容器。但是,我阅读的内容越多,社区使用IoC容器的压力就越大。
我玩过.NET容器,如StructureMap,NInject,Unity和Funq。我仍然看不到IoC容器如何使我的代码受益/改进。
我也害怕在工作中开始使用容器,因为许多同事会看到他们不理解的代码。他们中的许多人可能不愿意学习新技术。
请说服我,我需要使用IoC容器。与工作中的其他开发人员交谈时,我将使用这些参数。
Answers:
哇,简直无法相信乔尔会这样:
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());
of! 现在,将在有问题的对象的每个虚拟属性设置器上为您自动生成所有该手册INotifyPropertyChanged BS。
这是魔法吗? 是的!如果您可以相信该代码可以完成其工作,那么您可以安全地跳过所有包装mumbo-jumbo的属性。您有需要解决的业务问题。
使用IoC工具进行AOP的其他一些有趣用途:
我和你在一起,瓦迪姆 IoC容器采用了一个简单,优雅且有用的概念,并使其成为您必须学习200天的手册才能使用两天的东西。
我个人对IoC社区如何接受Martin Fowler撰写的精美优美的文章感到困惑,并将其变成一堆通常包含200-300页手册的复杂框架。
我尽量不要有判断力(HAHA!),但是我认为使用IoC容器的人(A)非常聪明,并且(B)对那些不像他们那么聪明的人缺乏同理心。一切对他们来说都是很有意义的,因此他们很难理解许多普通程序员会发现这些概念令人困惑。这是知识的诅咒。了解IoC容器的人很难相信有些人不了解它。
使用IoC容器最有价值的好处是,您可以在一个地方放置一个配置开关,以便在测试模式和生产模式之间进行切换。例如,假设您有两个版本的数据库访问类:一个版本在开发过程中经常使用日志记录并进行了大量验证,而另一个版本却没有记录或验证,而这对于生产而言却是惊人的快。能够在一处在它们之间切换是很高兴的。另一方面,这是一个相当琐碎的问题,可以轻松地以更简单的方式处理,而无需IoC容器的复杂性。
我相信,如果您使用IoC容器,坦率地说,您的代码将变得更难阅读。您必须查看的地方数目才能弄清楚代码要执行的操作至少增加了一个。在天堂的某个地方,天使在呼喊。
大概没有人强迫您使用DI容器框架。您已经在使用DI解耦类并提高可测试性,因此您将获得许多好处。简而言之,您倾向于简单,这通常是一件好事。
如果您的系统达到了使手工DI变得繁琐的复杂程度(即增加了维护),则应将其与DI容器框架的团队学习曲线进行权衡。
如果您需要对依赖项生存期管理进行更多控制(也就是说,如果您需要实现Singleton模式),请查看DI容器。
如果使用DI容器,请仅使用所需的功能。如果足够,请跳过XML配置文件并用代码对其进行配置。坚持构造函数注入。Unity或StructureMap的基础知识可以精简为几页。
Mark Seemann撰写了一篇很棒的博客文章:何时使用DI容器
在我看来,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类,而不是IMHO更加混乱的配置文件,则可能会有以下内容:
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容器也非常适合加载深层嵌套的类依赖项。例如,如果您使用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.
}
我是声明式编程的狂热者(看看我回答了多少个SQL问题),但是我看过的IoC容器似乎太不可思议了。
...或者也许IoC容器的开发人员无法编写清晰的文档。
...或者两者都在某种程度上是正确的。
我认为IoC容器的概念并不坏。但是,实现必须既强大又灵活,既要在各种应用程序中有用,又要简单易懂。
大概是六分之一,六分之一。实际的应用程序(而不是玩具或演示程序)注定是复杂的,需要考虑很多特殊情况和规则例外。要么将这种复杂性封装在命令性代码中,要么将其封装在声明性代码中。但是您必须在某个地方代表它。
在我看来,您已经建立了自己的IoC容器(使用Martin Fowler所描述的各种模式),并在问为什么别人的实现比您的更好。
因此,您有一堆可以使用的代码。并且想知道为什么要用其他人的实现代替它。
考虑第三方IoC容器的优点
缺点
因此,权衡利弊并做出决定。
我认为,使用IoC可以获得IoC的大部分价值。由于您已经在执行此操作,因此其余的好处是递增的。
您获得的价值将取决于您正在处理的应用程序的类型:
对于多租户,IoC容器可以处理一些基础架构代码,以加载不同的客户端资源。当您需要特定于客户端的组件时,请使用自定义选择器来处理逻辑,而不必担心客户端代码中的逻辑。您当然可以自己构建它,但这是IoC如何提供帮助的示例。
IoC具有许多扩展点,可用于从配置中加载组件。这是很常见的事情,但是工具是由容器提供的。
如果您想将AOP用于某些跨领域的问题,则IoC提供了挂钩来拦截方法调用。临时在项目上很少这样做,但IoC使其更容易。
我之前已经写过这样的功能,但是如果现在需要这些功能中的任何一个,我宁愿使用一个预先构建并经过测试的工具,如果它适合我的体系结构。
正如其他人所提到的,您还可以集中配置要使用的类。尽管这可能是一件好事,但它会导致误导和复杂化。大多数应用程序的核心组件并没有太多替换,因此要进行权衡有点困难。
我使用IoC容器并欣赏其功能,但不得不承认,我已经权衡利弊:我的代码在类级别变得更清晰,而在应用程序级别则变得不太清晰(即可视化控制流)。
我是一名正在康复的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 <>注入即可。
优点:
我建议持怀疑态度的人继续进行下一个绿色项目,并诚实地问自己在什么时候需要容器。稍后将IOC容器考虑在内很容易,因为您只是将工厂实现替换为IOC容器配置模块。
对我而言,使用IoC容器(个人而言,我使用Ninject)的最大好处是消除了设置和其他类型的全局状态对象的传递。
我不为网络编程,我的程序是一个控制台应用程序,在对象树深处的许多地方,我需要访问用户指定的设置或在对象树的完全独立的分支上创建的元数据。使用IoC时,我只是简单地告诉Ninject将设置视为一个单例(因为它们总是只有一个实例),在构造函数中请求设置或字典,然后保存...当我需要它们时,它们会神奇地出现!
如果不使用IoC容器,则必须先将设置和/或元数据向下传递到2、3,...,n个对象,然后才由需要它的对象实际使用它。
正如其他人在此处详细介绍的那样,DI / IoC容器还有许多其他好处,从创建对象的想法转变为请求对象的想法可能会弯腰,但是使用DI对我和我的团队非常有用,因此也许您可以添加它到你的武器库!
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
-part,它应该是一个警告信号,表明您的设计有问题。
要摆脱它(IO部分),获得IoC框架的所有优点,而没有缺点,您可以改为使用抽象工厂。
正确的解决方案是这样的
data Foo = Foo { apa :: Bar }
或许
data Foo = forall b. (IBar b) => Foo { apa :: b }
并注入(但我不称其为注入)吧。
另外:与LINQ的发明者Erik Meijer一起观看此视频,他说DI适用于不懂数学的人(我完全不同意): http //www.youtube.com/watch?v = 8Mttjyf-8P4
与Spolsky先生不同,我不相信使用IoC框架的人非常聪明-我只是相信他们不懂数学。
IBar
,实际上是IO IBar
),所以放弃了类型安全性。是的,在Haskell示例中我无法获得空引用。
我发现正确实现依赖注入会迫使程序员使用各种其他编程实践,这些实践有助于提高代码的可测试性,灵活性,可维护性和可伸缩性:诸如单一职责原则,关注点分离和编码之类的实践。针对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
,我可以测试它是否以预期的方式使用,而无需数据库连接或其他任何东西。ISimpleWorkflowInstanceMerger
接口的项目的引用。这使我可以将应用程序分解为单独的模块,并维护真正的多层体系结构,从而使事情变得更加灵活。一个典型的Web应用程序将具有很多控制器。随着应用程序的增长,在每个控制器中手动执行DI的所有痛苦都会加起来。如果您的应用程序只有一个上下文根,那么它永远不会尝试通过反射实例化服务,那么这并不是什么大问题。但是,除非使用某种类型的框架来管理依赖关系图,否则任何使用依赖关系注入的应用程序一旦达到一定大小,其管理成本就会变得非常昂贵。
每当您使用“ new”关键字时,您都在创建一个具体的类依赖关系,并且您的头脑中应该会有点警钟。孤立地测试该对象变得更加困难。解决方案是对接口进行编程并注入依赖关系,以便可以使用实现该接口的任何对象(例如,模拟)对对象进行单元测试。
问题是您必须在某个地方构造对象。工厂模式是将耦合移出POXO的一种方法(普通的“在此处插入OO语言”对象)。如果您和您的同事都在编写这样的代码,那么IoC容器就是您可以对代码库进行的下一个“增量改进”。它将所有讨厌的Factory样板代码从干净的对象和业务逻辑中移出。他们会爱上它。哎呀,就公司为什么喜欢它,并让所有人都热心进行公司演讲。
如果您的同事还没有做DI,那么我建议您先专注于此。广为宣传如何编写易于测试的简洁代码。干净的DI代码是困难的部分,一旦到达那儿,将对象接线逻辑从Factory类转移到IoC容器应该是相对简单的。
我恰好正处于取消自制的DI代码并将其替换为IOC的过程中。我可能已经删除了200行以上的代码,并用大约10行代码替换了。是的,我不得不对如何使用容器(Winsor)进行一些学习,但是我是一名工程师,致力于使用Internet技术。 21世纪,所以我已经习惯了。我大概花了大约20分钟的时间查看方法。这很值得我度过。
随着您继续解耦类并反转依赖关系,这些类将继续保持较小状态,并且“依赖关系图”的大小将继续增长。(这还不错。)使用IoC容器的基本功能可以使所有这些对象的连接变得微不足道,但是手动进行操作可能会非常繁重。例如,如果我想创建“ Foo”的新实例但需要一个“ Bar”,该怎么办。“ Bar”需要“ A”,“ B”和“ C”。而且每个人都需要其他3样东西,等等,等等(是的,我不能拿出好假的名字:))。
使用IoC容器为您构建对象图可大大降低复杂度,并将其推送到一次性配置中。我只是说“为我创建一个'Foo'”,它指出了构建一个“ Foo”所需要的。
有些人将IoC容器用于更多基础架构,这对于高级方案是很好的,但是在那些情况下,我同意它会混淆并且使新开发人员难以阅读和调试代码。
在.NET世界中,AOP不太流行,因此对于DI而言,无论您自己编写还是使用其他框架,框架都是您唯一的选择。
如果使用AOP,则可以在编译应用程序时进行注入,这在Java中更为常见。
DI有很多好处,例如减少耦合,因此单元测试更加容易,但是您将如何实现呢?您要自己使用反射吗?
所以,已经快三年了,是吗?
评论IoC框架的人中有50%不了解IoC和IoC框架之间的区别。我怀疑他们是否知道您可以在不部署到应用服务器的情况下编写代码
如果我们采用最受欢迎的Java Spring框架,那是将IoC配置从XMl移到代码中,现在看起来像这样
`@Configuration公共类AppConfig {
public @Bean TransferService transferService() {
return new TransferServiceImpl(accountRepository());
}
public @Bean AccountRepository accountRepository() {
return new InMemoryAccountRepository();
}
}`而且我们需要一个框架来做到这一点,为什么呢?
老实说,我发现并没有很多需要IoC容器的情况,大多数情况下,它们只是增加了不必要的复杂性。
如果您只是为了简化对象的构造而使用它,我不得不问,您是否在多个位置实例化了该对象?单身人士会不符合您的需求吗?您是否在运行时更改配置?(切换数据源类型等)。
如果是,那么您可能需要一个IoC容器。如果没有,那么您只是将初始化从开发人员可以轻松看到的地方移开了。
谁说接口总比继承好?假设您正在测试服务。为什么不使用构造函数DI,并使用继承创建依赖项的模拟?我使用的大多数服务仅具有一些依赖性。这样做单元测试这种方式阻止保持一吨的无用的接口,意味着你不具备使用ReSharper的快速查找方法的声明。
我相信,对于大多数实现而言,说IoC容器删除不需要的代码是一个神话。
首先,首先要设置容器。然后,您仍然必须定义每个需要初始化的对象。因此,您不必在初始化时保存代码,而是将其移动(除非多次使用您的对象。作为Singleton更好吗?)。然后,对于以这种方式初始化的每个对象,都必须创建并维护一个接口。
有人对此有任何想法吗?
ShippingCostCalculator
或ProductUIPresentation
,或任何其他策略/实现。
我将尝试找出为什么从我的角度来看国际奥委会可能不利的地方。
与其他所有内容一样,IOC容器(或者像爱因斯坦所说的I = OC ^ 2)是一个概念,您必须自己决定是否需要在代码中使用它。最近对IOC的时尚狂呼仅仅是时尚。不要迷恋时尚,那是第一。您可以在代码中实现无数种概念。首先,自从我开始编程以来,我就一直在使用依赖项注入,并且在该名称以该名称流行后才学会了该术语本身。依赖控制是一个非常古老的主题,迄今为止,它已经以万亿方式解决了,这取决于什么与什么脱钩。将一切从一切中分离出来是胡说八道。IOC容器的问题在于它试图像实体框架或NHibernate一样有用。虽然一旦必须将任何数据库与系统耦合就必须编写对象关系映射器,但IOC容器并非总是必需的。因此,当IOC容器有用时:
1:在您的代码中通常没有那么多依赖,或者在设计初期就意识到它们。当抽象思维到期时,抽象思维很有用。
2:将您的代码与第三方代码耦合是一个HuGe问题。我当时使用的代码已有10多年的历史了,那时的代码是高级和高级概念ATL,COM,COM +等。现在,您无法使用该代码。我的意思是说,先进的概念可以带来明显的优势,但是从长远来看,它已被过时的优势本身所抵消。它只是使所有这些都变得更加昂贵。
3:软件开发足够困难。如果允许一些高级概念植入代码中,则可以将其扩展到无法识别的水平。IOC2有问题。尽管它解耦了依赖关系,但它也解耦了逻辑流。假设您发现了一个错误,需要休息一下以检查情况。与其他高级概念一样,IOC2使其变得更加困难。修复概念中的错误比修复普通代码中的错误要困难得多,因为修复错误时,必须再次遵守概念。(仅举一个例子,C ++ .NET经常更改语法,以至于在重构某些旧版本的.NET之前您需要认真考虑。)那么,IOC的问题是什么?问题在于解决依赖关系。解决的逻辑通常隐藏在IOC2本身中,可能以不常见的方式编写,您需要学习和维护。您的第三方产品会在5年后出现吗?微软不是。
关于IOC2到处都写有“我们知道如何”综合症。这类似于自动化测试。花哨的时间和完美的解决方案乍看之下,您只需将所有测试整夜执行,然后在早晨查看结果即可。一个接一个的公司来解释自动化测试的真正含义真的很痛苦。自动化测试绝对不是减少可以一整夜引入的bug数量以提高产品质量的快速方法。但是,时尚使这一概念变得令人讨厌。IOC2患有相同的综合征。人们认为您需要实施它才能使软件保持良好状态。在最近一次采访中,我被问到是否正在实施IOC2和自动化。那是时尚的标志:公司已经用MFC编写了部分代码,他们不会放弃。
您需要像学习软件中的其他任何概念一样学习IOC2。是否需要使用IOC2的决定是在团队和公司内部。但是,在做出决定之前,必须至少提及上述所有论点。只有看到正面优势大于负面优势,您才能做出积极的决定。
IOC2没什么错,除了它仅解决了它解决的问题并介绍了它引入的问题。没有其他的。但是,违背时尚是非常困难的,他们有汗水的嘴巴,追随者。奇怪的是,当他们的幻想变得很明显时,他们中间没有一个人。软件行业中的许多概念得到了捍卫,因为它们创造了利润,撰写了书籍,举行了会议,制造了新产品。那就是时尚,通常是短暂的。人们一旦发现其他东西,便会完全放弃。IOC2很有用,但它显示出与我所见过的许多其他消失的概念相同的信号。我不知道它是否会生存。没有规则。您认为如果有用,它将生存。不,它不是那样的。一家大型的富裕公司就足够了,这个概念可能会在几周内消失。走着瞧。NHibernate幸存下来,EF位居第二。也许IOC2也将生存。不要忘记,软件开发中的大多数概念都没有什么特别的,它们非常合乎逻辑,简单明了,有时记住当前的命名约定比理解概念本身更加困难。IOC2的知识是否会使开发人员成为更好的开发人员?不可以,因为如果开发人员无法提出与IOC2本质上相似的概念,那么他或她将很难理解IOC2正在解决的问题,使用它看起来似乎是虚假的,他或她可能会开始使用它为了某种政治上的正确。不要忘记,软件开发中的大多数概念都没有什么特别的,它们非常合乎逻辑,简单明了,有时记住当前的命名约定比理解概念本身更加困难。IOC2的知识是否会使开发人员成为更好的开发人员?不可以,因为如果开发人员无法提出与IOC2本质上相似的概念,那么他或她将很难理解IOC2正在解决的问题,使用它看起来似乎是虚假的,他或她可能会开始使用它为了某种政治上的正确。不要忘记,软件开发中的大多数概念都没有什么特别的,它们非常合乎逻辑,简单明了,有时记住当前的命名约定比理解概念本身更加困难。IOC2的知识是否会使开发人员成为更好的开发人员?不可以,因为如果开发人员无法提出与IOC2本质上相似的概念,那么他或她将很难理解IOC2正在解决的问题,使用它看起来似乎是虚假的,他或她可能会开始使用它为了某种政治上的正确。IOC2的知识是否会使开发人员成为更好的开发人员?不可以,因为如果开发人员无法提出与IOC2本质上相似的概念,那么他或她将很难理解IOC2正在解决的问题,使用它看起来似乎是虚假的,他或她可能会开始使用它为了某种政治上的正确。IOC2的知识是否会使开发人员成为更好的开发人员?不可以,因为如果开发人员无法提出与IOC2本质上相似的概念,那么他或她将很难理解IOC2正在解决的问题,使用它看起来似乎是虚假的,他或她可能会开始使用它为了某种政治上的正确。
IOC容器解决了您可能尚未解决的问题,但这是一个不错的问题
您不需要实现依赖注入的框架。您也可以通过核心Java概念来执行此操作。 http://en.wikipedia.org/wiki/Dependency_injection#Code_illustration_using_Java
我意识到这是一篇比较老的文章,但它似乎仍然很活跃,我想我要提供其他一些尚未提及的观点。
我会说我同意依赖注入的好处,但是我确实更喜欢自己构造和管理对象,使用的模式与Maxm007在他的回答中概述的模式没有什么不同。我发现使用第三方容器存在两个主要问题:
1)让第三方库“自动”管理对象的生命周期可使其获得意想不到的结果。我们发现,尤其是在大型项目中,您可以拥有比预期更多的对象副本,并且比手动管理生命周期所拥有的副本还要多。我确信这取决于所使用的框架,但是问题仍然存在。如果您的对象拥有资源,数据连接等,这也会引起问题,因为该对象的寿命有时会比您预期的更长。因此,不可避免地,IoC容器往往会增加应用程序的资源利用率和内存占用量。
2)我认为IoC容器是“黑盒编程”的一种形式。我发现,特别是我们经验不足的开发人员往往会滥用它们。它使程序员不必考虑对象之间的关系或如何解耦,因为它为对象提供了一种机制,使他们可以简单地凭空获取任何想要的对象。例如,可能有很好的设计理由,ObjectA永远不应该直接了解ObjectB,而没有创建工厂,网桥或服务定位器,没有经验的程序员只会说:“没问题,我只是从IoC容器中获取ObjectB。 ”。实际上,这可能导致对象耦合增加,这是IoC可以防止的。