598

我一直在使用依赖注入(DI),在构造函数、属性或方法中注入。我从来没有觉得需要使用控制反转(IoC) 容器。然而,我读得越多,我就越觉得来自社区的使用 IoC 容器的压力越大。

我使用过 .NET 容器,例如StructureMapNInjectUnityFunq。我仍然看不到 IoC 容器将如何使我的代码受益/改进。

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

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

4

30 回答 30

441

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

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 都会在相关对象的每个虚拟属性设置器上为您自动生成。

这是魔法吗? 是的!如果您可以相信此代码可以完成其工作这一事实,那么您可以安全地跳过所有包装 mumbo-jumbo 的属性。你有业务问题需要解决。

使用 IoC 工具执行 AOP 的其他一些有趣用途:

  • 声明式和嵌套数据库事务
  • 声明式和嵌套的工作单元
  • 日志记录
  • 前置/后置条件(按合同设计)
于 2009-10-07T15:10:50.297 回答
137

我和你在一起,瓦迪姆。IoC 容器采用了一个简单、优雅且有用的概念,并通过一本 200 页的手册将其变成你必须学习两天的东西。

我个人对 IoC 社区如何将Martin Fowler 的一篇漂亮、优雅的文章转化为一堆复杂的框架感到困惑,这些框架通常有 200-300 页的手册。

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

使用 IoC 容器最有价值的好处是您可以在一个地方进行配置切换,让您可以在测试模式和生产模式之间进行切换。例如,假设您有两个版本的数据库访问类……一个版本积极记录并进行大量验证,您在开发过程中使用它,另一个版本没有记录或验证,这对于生产来说非常快。很高兴能够在一个地方在它们之间切换。另一方面,这是一个相当简单的问题,可以通过更简单的方式轻松处理,而无需 IoC 容器的复杂性。

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

于 2009-05-16T01:21:30.480 回答
37

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

如果您的系统达到了手动 DI 变得繁琐(即增加维护)的复杂程度,请将其与 DI 容器框架的团队学习曲线进行权衡。

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

如果您使用 DI 容器,请仅使用您需要的功能。如果足够,请跳过 XML 配置文件并在代码中对其进行配置。坚持构造函数注入。Unity 或 StructureMap 的基础知识可以浓缩为几页。

Mark Seemann 有一篇很棒的博文:When to use a DI Container

于 2009-09-19T02:32:38.257 回答
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
}

如果您使用静态 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.
   }
}
于 2009-05-16T01:13:12.653 回答
32

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 容器中,您可以 Resolve 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.
}
于 2009-05-16T02:12:21.357 回答
28

我是声明式编程的粉丝(看看我回答了多少 SQL 问题),但是我看过的 IoC 容器对于它们自身来说似乎太神秘了。

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

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

我不认为 IoC 容器的概念不好。但是实现必须足够强大(即灵活)以在各种应用程序中有用,而且简单且易于理解。

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

于 2009-05-16T01:41:16.813 回答
27

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

  • 减少毛球主程序启动例程。
  • 启用相当深入的部署时重新配置功能。
  • 使依赖注入风格成为新工作阻力最小的路径。

当然,也可能有困难:

  • 需要复杂的启动/关闭/生命周期管理的代码可能不容易适应容器。
  • 您可能必须解决任何个人、流程和团队文化问题——但是,这就是您问...
  • 一些工具包本身正在迅速成为重量级工具,鼓励了许多 DI 容器开始时反对的那种深度依赖。
于 2009-05-16T01:19:48.723 回答
23

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

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

考虑第三方 IoC 容器的优点

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

缺点

  • 您会免费获得错误介绍:)
  • 库设计可能比你的差
  • 你必须学习一个新的 API
  • 太多你永远不会使用的功能
  • 调试不是你写的代码通常更难
  • 从以前的 IoC 容器迁移可能很乏味

因此,权衡利弊并做出决定。

于 2009-09-19T02:15:38.623 回答
17

我认为 IoC 的大部分价值是通过使用 DI 获得的。由于您已经这样做了,其余的好处是增量的。

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

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

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

  • 如果你想使用 AOP 处理一些横切关注点,IoC 提供了钩子来拦截方法调用。这在项目中不太常见,但 IoC 使它更容易。

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

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

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

于 2009-05-16T04:40:52.733 回答
16

我是一个正在恢复的国际奥委会瘾君子。这些天来,我发现在大多数情况下很难证明将 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 容器配置模块替换工厂实现。

于 2013-01-10T18:30:08.390 回答
14

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

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

如果不使用 IoC 容器,我将不得不将设置和/或元数据向下传递到 2、3、...、n 个对象,然后才能被需要它的对象实际使用。

DI/IoC 容器还有许多其他好处,正如其他人在此处详述的那样,从创建对象到请求对象的想法可能令人费解,但使用 DI 对我和我的团队非常有帮助,所以也许你可以添加它到你的武器库!

于 2009-10-08T21:22:15.390 回答
14

如果你想的话,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 并且 is 应该是一个警告信号,表明你的设计有问题,如果你得到它。

要摆脱它(IO 部分),获得 IoC 框架的所有优点并且没有它的缺点,您可以改用抽象工厂。

正确的解决方案是

data Foo = Foo { apa :: Bar }

或许

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

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

另外:请看 Erik Meijer(LINQ 的发明者)的视频,他说 DI 适合不懂数学的人(我完全同意):http ://www.youtube.com/watch?v =8Mttjyf-8P4

与 Spolsky 先生不同,我不相信使用 IoC 框架的人非常聪明——我只是相信他们不懂数学。

于 2009-10-13T09:20:29.140 回答
10

我发现正确实现依赖注入往往会迫使程序员使用各种其他有助于提高代码的可测试性、灵活性、可维护性和可扩展性的编程实践:单一职责原则、关注点分离和编码等实践针对 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 框架,请注意:

  • 我可以对这个类进行单元测试。通过创建一个 mock ISimpleWorkflowInstanceMerger,我可以测试它是否按照我预期的方式使用,而不需要数据库连接或任何东西。
  • 我使用的代码少得多,而且代码更容易阅读。
  • 如果我的依赖项的依赖项之一发生更改,我不必对控制器进行任何更改。当您考虑到多个控制器可能使用某些相同的依赖项时,这尤其好。
  • 我从不从我的数据层明确引用类。我的 Web 应用程序可以只包含对包含ISimpleWorkflowInstanceMerger接口的项目的引用。这使我可以将应用程序分解为单独的模块,并维护一个真正的多层架构,这反过来又使事情变得更加灵活。

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

于 2010-06-17T22:50:13.630 回答
9

每当您使用“new”关键字时,您都在创建一个具体的类依赖项,并且您的脑海中应该会响起一个小警钟。孤立地测试这个对象变得更加困难。解决方案是对接口进行编程并注入依赖项,以便可以使用任何实现该接口的对象(例如模拟)对对象进行单元测试。

问题是你必须在某处构造对象。工厂模式是将耦合从您的 POXO 中转移出来的一种方式(普通旧的“在此处插入您的 OO 语言”对象)。如果您和您的同事都在编写这样的代码,那么 IoC 容器是您可以对代码库进行的下一个“增量改进”。它会将所有讨厌的 Factory 样板代码从您的干净对象和业务逻辑中移出。他们会得到它并喜欢它。哎呀,让公司谈谈你为什么喜欢它,让每个人都兴奋不已。

如果你的同事还没有做 DI,那么我建议你先关注它。传播有关如何编写易于测试的干净代码的信息。干净的 DI 代码是最困难的部分,一旦你到了那里,将对象连接逻辑从工厂类转移到 IoC 容器应该是相对微不足道的。

于 2009-10-07T19:22:05.347 回答
8

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

于 2009-05-16T01:19:13.420 回答
7

不需要IoC 容器。

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

无论如何,这通常是使用库/框架的最佳时机 - 当您了解它在做什么并且可以在没有库的情况下做到这一点时。

于 2009-12-17T05:56:48.050 回答
6

我恰好正在抽出自制的 DI 代码并用 IOC 替换它。我可能已经删除了 200 多行代码,并用大约 10 行代码替换了它。是的,我必须学习一些关于如何使用容器 (Winsor) 的知识,但我是一名在互联网技术领域工作的工程师21世纪,习惯了。我可能花了大约 20 分钟来查看操作方法。这非常值得我花时间。

于 2009-10-07T18:38:57.010 回答
5

随着您继续解耦类并反转依赖项,类继续保持较小,“依赖关系图”的大小继续增长。(这还不错。)使用 IoC 容器的基本功能使得连接所有这些对象变得微不足道,但是手动完成会变得非常繁重。例如,如果我想创建一个“Foo”的新实例,但它需要一个“Bar”。一个“酒吧”需要一个“A”、“B”和“C”。而且每一个都需要其他 3 件东西,等等(是的,我想不出好的假名 :))。

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

有些人将 IoC 容器用于更多的基础设施,这对于高级场景来说很好,但在这些情况下,我同意它可能会混淆并使新开发人员难以阅读和调试代码。

于 2009-10-07T15:10:20.563 回答
4

关于 Unity 的同上。太大了,你可以听到椽子的吱吱声。

当人们开始谈论 IoC 代码看起来多么干净时,我永远不会感到惊讶,这与那些曾经谈论 C++ 中的模板是如何回到 90 年代的优雅方式的人是同一类人,但如今却将它们谴责为晦涩难懂. 呸!

于 2009-10-08T16:50:26.343 回答
2

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

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

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

于 2009-05-16T05:01:44.727 回答
2

所以,已经快3年了吧?

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

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

`@Configuration 公共类 AppConfig {

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

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

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

于 2013-07-21T23:37:50.470 回答
1

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

如果您只是将它用于简化对象的构造,我不得不问,您是否在多个位置实例化该对象?单身人士会不适合您的需求吗?您是否在运行时更改配置?(切换数据源类型等)。

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

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

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

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

有人对此有任何想法吗?

于 2013-03-08T18:52:46.920 回答
1

如果您需要集中配置依赖项,那么您将需要一个 IoC 容器,以便可以轻松地将它们大量换出。这在 TDD 中最有意义,其中许多依赖项被换出,但依赖项之间几乎没有相互依赖关系。这样做的代价是在一定程度上混淆了对象构造的控制流程,因此组织良好且文档合理的配置很重要。有理由这样做也不错,否则就是抽象的镀金。我已经看到它做得很差,以至于它被拖到相当于构造函数的 goto 语句。

于 2013-08-12T23:34:35.133 回答
1

就是为什么。该项目称为 IOC-with-Ninject。您可以下载并使用 Visual Studio 运行它。此示例使用 Ninject,但所有“新”语句都在一个位置,您可以通过更改要使用的绑定模块来完全更改应用程序的运行方式。该示例已设置,因此您可以绑定到服务的模拟版本或真实版本。在小型项目中可能无关紧要,但在大型项目中却很重要。

需要明确的是,我所看到的优势:1)所有 语句都位于代码根目录的一个位置。2) 一次更改就完全重构代码。3)“酷因素”的加分,因为它......好吧:酷。:p

于 2013-09-10T13:26:39.637 回答
1

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

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

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

1:您的代码中并不经常有如此多的依赖关系,或者您在设计早期就意识到它们。当抽象思维到期时,抽象思维是有用的。

2:将您的代码与第三方代码耦合是一个巨大的问题。我使用的代码已经有 10 多年的历史了,并且当时遵循的是 ATL、COM、COM+ 等花哨的高级概念。您现在无法使用该代码执行任何操作。我要说的是,一个先进的概念会带来明显的优势,但从长远来看,这会因为过时的优势本身而被取消。它只是让这一切变得更加昂贵。

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

关于IOC2,“我们知道如何”综合症到处都是。这类似于自动化测试。乍一看很花哨的术语和完美的解决方案,您只需将所有测试放在晚上执行,然后在早上查看结果。一个接一个地解释自动化测试的真正含义真的很痛苦。自动化测试绝对不是减少您可以在一夜之间引入以提高产品质量的错误数量的快速方法。但是,时尚正在让这个概念变得恼人地占据主导地位。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 正在解决哪个问题,使用它会显得人为,他或她可能会开始使用它为了某种政治正确。

于 2014-02-11T15:42:31.213 回答
0

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

这可能不是 IoC 的用途,但这是我发现自己最常使用它的用途。

于 2009-10-07T17:53:51.013 回答
0

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

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

于 2013-11-25T00:47:02.573 回答
0

你不需要一个框架来实现依赖注入。您也可以通过核心 Java 概念来做到这一点。 http://en.wikipedia.org/wiki/Dependency_injection#Code_illustration_using_Java

于 2014-02-07T05:13:28.380 回答
0

我意识到这是一篇相当老的帖子,但它似乎仍然相当活跃,我想我会贡献一些其他答案中尚未提到的观点。

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

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

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

于 2014-03-27T19:41:25.977 回答
-8

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

于 2009-10-07T15:20:05.177 回答