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

半腔热情 提交于 2020-01-06 15:40:05

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

我一直在使用依赖注入 (DI)一段时间,在构造函数,属性或方法中注入。 我从未觉得需要使用控制反转 (IoC)容器。 但是,我读的越多,我觉得社区使用IoC容器的压力就越大。

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

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

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


#1楼

我是一名康复的国际奥委会成瘾者。 这些天我发现很难证明在大多数情况下使用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();
        }
    }
}

您的容器配置是您的抽象工厂实现,您的注册是抽象成员的实现。 如果需要新的单例依赖项,只需将另一个抽象属性添加到抽象工厂即可。 如果您需要瞬态依赖,只需添加另一个方法并将其作为Func <>注入。

好处:

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

我建议怀疑论者给它一个下一个绿色的田野项目,并诚实地问自己,你需要哪个容器。 稍后您可以轻松地将IOC容器考虑在内,因为您只是使用IOC容器配置模块替换工厂实现。


#2楼

听起来像你已经建立了自己的IoC容器 (使用Martin Fowler描述的各种模式)并且问为什么其他人的实现比你的更好。

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

考虑第三方IoC容器的优点

  • 你得到免费修复的bug
  • 图书馆设计可能比你的更好
  • 人们可能已经熟悉特定的图书馆
  • 图书馆可能比你的快
  • 它可能有一些你希望你实现但没有时间的功能(你有一个服务定位器吗?)

缺点

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

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


#3楼

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

如果您的系统达到复杂程度,手动DI成为一项杂务(即增加维护),请根据DI容器框架的团队学习曲线进行权衡。

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

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

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


#4楼

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

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

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

谁说接口比继承更好呢? 假设您正在测试服务。 为什么不使用构造函数DI,并使用继承创建依赖项的模拟? 我使用的大多数服务只有一些依赖项。 这样做单元测试这种方式阻止保持一吨的无用的接口,意味着你不必使用ReSharper的快速查找方法的声明。

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

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

有没有人对此有任何想法?


#5楼

当您继续解耦类并反转依赖关系时,类继续保持较小并且“依赖关系图”的大小继续增长。 (这还不错。)使用IoC容器的基本功能可以使所有这些对象的连接变得微不足道,但手动执行此操作会非常繁琐。 例如,如果我想创建“Foo”的新实例但需要“Bar”,该怎么办? 而“Bar”需要“A”,“B”和“C”。 而且每个人都需要其他3个等等(是的,我不能想出好的假名:))。

使用IoC容器为您构建对象图可以降低复杂性,并将其推送到一次性配置中。 我只是简单地说“给我一个'Foo'”,它会找出构建一个所需要的东西。

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!