Vad*_*dim 598 dependency-injection ioc-container inversion-of-control
我一直在使用依赖注入(DI)一段时间,在构造函数,属性或方法中注入.我从未觉得需要使用控制反转(IoC)容器.但是,我读的越多,我觉得社区使用IoC容器的压力就越大.
我使用.NET容器,如StructureMap,NInject,Unity和Funq.我仍然没有看到IoC容器如何使我的代码受益/改进.
我也害怕在工作中开始使用容器,因为我的许多同事都会看到他们不理解的代码.他们中的许多人可能不愿意学习新技术.
请说服我需要使用IoC容器.当我在工作中与开发人员交谈时,我将使用这些论点.
Ben*_*man 441
哇,简直不敢相信乔尔会赞成这个:
var svc = new ShippingService(new ProductLocator(),
new PricingService(), new InventoryService(),
new TrackingRepository(new ConfigProvider()),
new Logger(new EmailLogger(new ConfigProvider())));
Run Code Online (Sandbox Code Playgroud)
对此:
var svc = IoC.Resolve<IShippingService>();
Run Code Online (Sandbox Code Playgroud)
许多人没有意识到您的依赖链可能会嵌套,并且很快就会手动连接它们变得难以处理.即使有工厂,代码的重复也是不值得的.
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; }
}
Run Code Online (Sandbox Code Playgroud)
..结束了这个:
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;
}
Run Code Online (Sandbox Code Playgroud)
这是令人厌恶的管道代码,我坚持认为,如果你手工编写这样的代码,那么你就是在窃取你的客户端.有更好,更聪明的工作方式.
曾经听过这个词,工作更聪明,而不是更难?
想象一下,你团队中的一些聪明人出现说:"这是一种更简单的方法"
如果你让你的属性变得虚拟(冷静下来,这不是什么大不了的事),那么我们就可以自动编织这个属性行为了.(这称为AOP,但不要担心名称,请关注它将为您做什么)
根据您使用的IoC工具,您可以执行以下操作:
var bindingFriendlyInstance = IoC.Resolve<Customer>(new NotifyPropertyChangedWrapper());
Run Code Online (Sandbox Code Playgroud)
噗! 所有手册INotifyPropertyChanged BS现在都可以在相关对象的每个虚拟属性设置器上自动生成.
这太神奇了吗? 是的!如果您可以相信这段代码能够完成它的工作,那么您可以安全地跳过所有包含mumbo-jumbo的属性.你有业务问题需要解决.
使用IoC工具进行AOP的其他一些有趣的用途:
Joe*_*sky 138
我和你在一起,瓦迪姆.IoC容器采用简单,优雅和有用的概念,并使用200页的手册使您需要学习两天.
我个人很困惑IoC社区如何阅读Martin Fowler撰写的一篇优美,优雅的文章,并将其转化为一系列复杂的框架,通常包含200-300页的手册.
我尽量不做评判(哈哈!),但我认为使用IoC容器的人是(A)非常聪明,(B)对那些不那么聪明的人缺乏同情心.一切都对他们完全有意义,所以他们很难理解许多普通程序员会发现这些概念令人困惑.这是知识的诅咒.了解IoC容器的人很难相信有些人不理解它.
使用IoC容器最有价值的好处是,您可以在一个位置使用配置开关,这样您就可以在测试模式和生产模式之间进行切换.例如,假设您有两个版本的数据库访问类...一个版本进行了积极记录并进行了大量验证(在开发过程中使用),另一个版本没有记录或验证,这对于生产来说非常快.很高兴能够在一个地方之间切换它们.另一方面,这是一个相当简单的问题,可以在没有IoC容器复杂性的情况下以更简单的方式轻松处理.
我相信如果你使用IoC容器,坦率地说,你的代码很难阅读.您必须查看以确定代码尝试执行的操作的位置数量至少增加一个.在天堂的某个地方,一个天使喊出来.
Tru*_*ill 37
据推测,没有人强迫你使用DI容器框架.您已经在使用DI来分离类并提高可测试性,因此您可以获得许多好处.简而言之,你喜欢简单,这通常是一件好事.
如果您的系统达到复杂程度,手动DI成为一项杂务(即增加维护),请根据DI容器框架的团队学习曲线进行权衡.
如果您需要更多地控制依赖关系生存期管理(也就是说,如果您觉得需要实现Singleton模式),请查看DI容器.
如果您使用DI容器,请仅使用您需要的功能.如果足够,请跳过XML配置文件并在代码中对其进行配置.坚持构造函数注入.Unity或StructureMap的基础知识可以缩减为几页.
Mark Seemann有一篇很棒的博客文章:何时使用DI容器
ben*_*wey 32
在我看来,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
}
Run Code Online (Sandbox Code Playgroud)
如果你使用静态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
}
Run Code Online (Sandbox Code Playgroud)
然后,你的静态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.
}
}
Run Code Online (Sandbox Code Playgroud)
ben*_*wey 32
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 { }
Run Code Online (Sandbox Code Playgroud)
如果您已将所有这些依赖项加载到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.
}
Run Code Online (Sandbox Code Playgroud)
Bil*_*win 28
我是声明性编程的粉丝(看看我回答了多少个SQL问题),但我看过的IoC容器对于他们自己的好处似乎太神秘了.
...或者IoC容器的开发人员可能无法编写清晰的文档.
......或者两者都适用于某种程度.
我不认为IoC容器的概念是坏的.但实现必须足够强大(即灵活),足以在各种应用程序中使用,但简单易懂.
它可能是六个中的六个,另外六个.真正的应用程序(不是玩具或演示)必然是复杂的,可以解决许多极端情况和规则异常.要么将这种复杂性封装在命令式代码中,要么将其封装在声明性代码中.但你必须在某个地方代表它.
Sam*_*ron 23
听起来像你已经建立了自己的IoC容器(使用Martin Fowler描述的各种模式)并且问为什么别人的实现比你的更好.
所以,你有一堆已经有效的代码.并且想知道为什么你想要用其他人的实现替换它.
考虑第三方IoC容器的优点
缺点
所以,权衡你的利弊与你的利弊并做出决定.
Ste*_*ons 17
我认为通过使用DI可以获得IoC的大部分价值.既然你已经这样做了,剩下的好处就是增量.
您获得的值取决于您正在处理的应用程序类型:
对于多租户,IoC容器可以处理一些用于加载不同客户端资源的基础结构代码.当您需要特定于客户端的组件时,请使用自定义选择器来处理逻辑,而不必担心客户端代码.你当然可以自己构建这个,但这里有一个IoC如何提供帮助的例子.
有许多可扩展点,IoC可用于从配置加载组件.这是常见的构建方法,但容器提供了工具.
我以前写过这样的功能,但如果我现在需要这些功能,我宁愿使用预先构建并经过测试的工具,如果它适合我的架构.
如其他人所述,您还可以集中配置要使用的类.虽然这可能是一件好事,但它的代价是误导和复杂化.大多数应用程序的核心组件都没有被替换太多,因此需要进行权衡取舍.
我使用IoC容器并欣赏功能,但必须承认我已经注意到了权衡:我的代码在类级别变得更清晰,在应用程序级别变得更加清晰(即可视化控制流程).
Max*_*007 16
我是一名康复的国际奥委会成瘾者.这些天我发现很难证明在大多数情况下使用IOC来证明其合理性.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();
}
}
}
Run Code Online (Sandbox Code Playgroud)
您的容器配置是您的抽象工厂实现,您的注册是抽象成员的实现.如果需要新的单例依赖项,只需将另一个抽象属性添加到抽象工厂即可.如果您需要瞬态依赖,只需添加另一个方法并将其作为Func <>注入.
好处:
我建议怀疑论者给它一个下一个绿色的田野项目,并诚实地问自己,你需要哪个容器.稍后您可以轻松地将IOC容器考虑在内,因为您只是使用IOC容器配置模块替换工厂实现.
Jef*_*ron 14
对我来说使用IoC容器的最大好处(我个人使用Ninject)就是消除设置和其他类型的全局状态对象的传递.
我没有为web编程,我是一个控制台应用程序,在对象树深处的许多地方,我需要访问在对象树的完全独立的分支上创建的用户指定的设置或元数据.使用IoC,我只是告诉Ninject将设置视为一个单例(因为它们总是只有一个实例),请在构造函数中请求设置或字典并预先设置......当我需要它们时它们会神奇地出现!
在不使用IoC容器的情况下,我必须将设置和/或元数据向下传递到2,3,...,n个对象,然后才能被需要它的对象使用.
DI/IoC容器还有许多其他好处,正如其他人在这里详细介绍的那样,从创建对象到请求对象的想法可能会令人费解,但使用DI对我和我的团队非常有帮助,所以也许你可以添加它到你的武器库!
fin*_*son 14
如果你想要IoC框架是很好的...
......丢掉类型安全.许多(所有?)IoC框架强制您执行代码,如果您想确定所有内容都已正确连接."嘿!希望我已经完成了所有设置,因此我对这100个类的初始化不会在生产中失败,抛出空指针异常!"
...您的垃圾用全局代码(IoC框架是所有有关更改全局状态).
...编写糟糕的代码与不清楚的依赖,这很难重构,因为你永远不知道什么取决于什么.
IoC的问题在于使用它们的人习惯于编写这样的代码
public class Foo {
public Bar Apa {get;set;}
Foo() {
Apa = new Bar();
}
}
Run Code Online (Sandbox Code Playgroud)
这显然是有缺陷的,因为Foo和Bar之间的依赖是硬连线的.然后他们意识到编写代码会更好
public class Foo {
public IBar Apa {get;set;}
Foo() {
Apa = IoC<IBar>();
}
}
Run Code Online (Sandbox Code Playgroud)
这也有缺陷,但不太明显.在Haskell中,类型Foo()会是,IO Foo但你真的不想要IO-part而且应该是一个警示标志,如果你得到它,你的设计有问题.
要摆脱它(IO部分),获得IoC框架的所有优点,并且没有任何缺点,你可以改为使用抽象工厂.
正确的解决方案就是这样的
data Foo = Foo { apa :: Bar }
Run Code Online (Sandbox Code Playgroud)
或者可能
data Foo = forall b. (IBar b) => Foo { apa :: b }
Run Code Online (Sandbox Code Playgroud)
和注入(但我不会称之为注入)吧.
另外:与Erik Meijer(LINQ的发明者)一起观看此视频,他说DI适用于不懂数学的人(我不能同意):http://www.youtube.com/watch?v = 8Mttjyf-8P4
与Spolsky先生不同,我不相信使用IoC框架的人非常聪明 - 我只是相信他们不懂数学.
Str*_*ior 10
我发现正确实现依赖注入往往会迫使程序员使用各种其他编程实践来帮助提高代码的可测试性,灵活性,可维护性和可伸缩性:单一责任原则,关注点分离和编码等实践反对API.感觉就像我被迫编写更多模块化,一口大小的类和方法,这使得代码更容易阅读,因为它可以采用一口大小的块.
但它也倾向于创建相当大的依赖树,这些依赖树通过框架(特别是如果使用约定)比通过手工更容易管理.今天我想在LINQPad中快速测试一些东西,我觉得创建一个内核并加载到我的模块中太麻烦了,我最后手工编写了这个:
var merger = new SimpleWorkflowInstanceMerger(
new BitFactoryLog(typeof(SimpleWorkflowInstanceMerger).FullName),
new WorkflowAnswerRowUtil(
new WorkflowFieldAnswerEntMapper(),
new ActivityFormFieldDisplayInfoEntMapper(),
new FieldEntMapper()),
new AnswerRowMergeInfoRepository());
Run Code Online (Sandbox Code Playgroud)
回想起来,使用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())
}
...
}
Run Code Online (Sandbox Code Playgroud)
现在将其与允许DI框架为您进行比较:
public MyController : Controller
{
private readonly ISimpleWorkflowInstanceMerger _simpleMerger;
public MyController(ISimpleWorkflowInstanceMerger simpleMerger)
{
_simpleMerger = simpleMerger;
}
...
}
Run Code Online (Sandbox Code Playgroud)
使用DI框架,请注意:
ISimpleWorkflowInstanceMerger,我可以测试它是否按照我预期的方式使用,而不需要数据库连接或任何东西.ISimpleWorkflowInstanceMerger接口的项目的引用.这使我可以将应用程序分解为单独的模块,并维护真正的多层体系结构,从而使事情更加灵活.典型的Web应用程序将拥有相当多的控制器.在每个控制器中手动执行DI的所有痛苦都会随着应用程序的增长而增加.如果您的应用程序只有一个上下文根,它从不尝试通过反射实例化服务,那么这不是一个大问题.然而,任何使用依赖注入的应用程序一旦达到一定的大小就会变得非常昂贵,除非你使用某种框架来管理依赖图.
每当你使用"new"关键字时,你就会创建一个具体的类依赖关系,并且你的脑子里会出现一个小闹铃.单独测试这个对象变得更加困难.解决方案是编程接口并注入依赖关系,以便可以使用实现该接口的任何东西(例如,模拟)对对象进行单元测试.
麻烦的是你必须在某处构建对象.工厂模式是将耦合移出POXO的一种方法(Plain Old"在此处插入您的OO语言"对象).如果您和您的同事都在编写这样的代码,那么IoC容器就是您可以对代码库进行的下一个"增量改进".它会将所有令人讨厌的Factory样板代码移出干净的对象和业务逻辑.他们会得到它并喜欢它.哎呀,让公司谈谈你为什么喜欢它并让每个人都热情好客.
如果你的同事还没有做DI,那么我建议你先关注它.传播如何编写易于测试的干净代码.清洁DI代码是困难的部分,一旦你在那里,将对象布线逻辑从Factory类转移到IoC容器应该是相对微不足道的.
您不需要 IoC容器.
但是如果你严格遵循DI模式,你会发现有一个会删除大量冗余,无聊的代码.
无论如何,这通常是使用库/框架的最佳时间 - 当您了解它正在做什么并且可以在没有库的情况下完成它.
我恰好正在掏出本土的DI代码并用IOC代替它.我可能已经删除了超过200行代码,并与约10是取代它,我必须做学习如何使用容器(南联)的一点点,但我是一个工程师,在互联网技术方面的工作21世纪所以我已经习惯了.我可能花了大约20分钟来看看如何.这非常值得我花时间.
当您继续解耦类并反转依赖关系时,类继续保持较小并且"依赖关系图"的大小继续增长.(这还不错.)使用IoC容器的基本功能可以使所有这些对象的连接变得微不足道,但手动执行此操作会非常繁琐.例如,如果我想创建"Foo"的新实例但需要"Bar",该怎么办?而"Bar"需要"A","B"和"C".而且每个人都需要其他3个等等(是的,我不能想出好的假名:)).
使用IoC容器为您构建对象图可以降低复杂性,并将其推送到一次性配置中.我只是简单地说"给我一个'Foo'",它会找出构建一个所需要的东西.
有些人使用IoC容器来获得更多的基础设施,这对于高级场景来说很好,但在这些情况下我同意它可能会混淆并使代码难以阅读和调试新的开发人员.
| 归档时间: |
|
| 查看次数: |
249393 次 |
| 最近记录: |