【问题标题】:Why are IOC containers unnecessary with dynamic languages为什么动态语言不需要 IOC 容器
【发布时间】:2011-01-17 10:29:51
【问题描述】:

在 Herding Code 播客第 68 期 http://herdingcode.com/herding-code-68-new-year-shenanigans/ 中,有人表示 IOC 容器不适用于 Python 或 Javascript 或类似的词语。我假设这是传统智慧,它适用于所有动态语言。为什么?动态语言使 IOC 容器变得不必要的原因是什么?

【问题讨论】:

标签: ioc-container dynamic-languages


【解决方案1】:

IoC 提供了一种机制来打破当一个对象在另一个类上调用“new”时获得的耦合。这种耦合将调用对象与其实现的任何接口的实例化实现联系在一起。

在静态语言中,当您按名称引用类时(在其上调用new),没有歧义。这是与特定类的紧密耦合

在动态语言中,调用new X 是一个占位符,用于“在执行点实例化任何定义为X 的类”。这是一个松散耦合,因为它只与名称 X 耦合。

这种细微的差别意味着,在动态语言中,您通常可以更改 X 是什么,因此关于实例化哪个类的决定仍然可以在调用类之外进行修改。

但是,我个人发现 IoC 有两个优点,而依靠动态语言来允许注入是我无法获得的。

通过构造函数传递依赖项的副作用之一是您最终会得到非常解耦、可重用且易于测试的“构建块”类。他们不知道他们打算在什么上下文中使用,所以你可以在所有地方重复使用它们。

另一个结果是有明确的代码来进行接线。正确完成,这清楚地代表了您的应用程序的结构,并将其分解为子系统和生命周期。这使得人们明确地决定他们想要将他们的类与哪个生命周期或子系统关联(在编写接线代码时),并在编写类时专注于对象的行为。

就像 Jörg W Mittag 所说.. “那些工具是不必要的,设计原则不是。” 我认为它们是不必要的,但做得对,仍然有价值。

【讨论】:

  • 这个问题不是专门针对 IoC containers 而不是 IoC 的概念吗?
  • 出色的答案。 .关于工具:测试框架、模拟库等也是如此。没有它们你也可以完成工作,但好的工具是无价的。
【解决方案2】:

我有不同的看法。我认为 IOC 容器肯定在动态语言中发挥作用。

我不同意这样一种观点,即动态语言消除了对结构清晰的对象组合的需求。或者动态语言“提供”相同的功能。

IOC 容器只是管理该组织的工具。

即使在动态语言中,我也想将组件“连接”在一起。无需在这些组件之间建立硬依赖关系。或者甚至没有为这些组件指定实际的实现类。

【讨论】:

  • 我同意一点。我相信即使在动态语言中,我们也需要将布线与组件分开。我不相信 IoC 容器是做到这一点的最佳方式。您真正需要的只是用于软件布线的内部 DSL。现有的 IoC 容器并不完全符合该描述。
  • IoC 容器通过提供用于完成任务的模式来促进某些功能。尽管动态语言可能包含一些功能,这些功能使得通常使用静态类型语言的 IoC 容器完成的某些任务变得不必要,但许多任务和模式在动态语言中仍然有用。具体例子见this question
【解决方案3】:

我同意上面的答案,但我想我也会在测试方面加入一点:

在子系统之间存在交互的复杂系统中,依赖注入是我所知道的进行单元测试的最佳方式。

如果您有一个与逻辑单元 Y 交互的逻辑单元 X,您可以创建一个具有预定义行为的 MockY 并显式测试 X 的逻辑。

没有依赖注入,编写测试是一场噩梦。您无法获得良好的代码覆盖率。一些框架(例如 django)通过启动模拟数据库实例来进行测试等来解决这个问题,但这基本上是一个糟糕的问题解决方案。

应该有两种测试:

  • 在任何环境中运行并测试各个代码单元的逻辑的单元测试。
  • 测试组合应用逻辑的集成/功能测试。

现在问题是:IoC。 IoC 有什么用?它在一些事情上很方便,但对于让依赖注入更容易使用来说真的很有用:

// Do this every time you want an instance of myServiceType
var SystemA = new SystemA()
var SystemB = new SystemB()
var SystemC = new SystemC(SystemA, "OtherThing")
var SystemD = new SystemD(SystemB, SystemC)
var IRepo = new MySqlRepo()
var myService = new myServiceType(SystemD, IRepo)

进入这个逻辑:

// Do this at application start
Container.Register(ISystemA, SystemA)
Container.Register(ISystemB, SystemB)
Container.Register(ISystemC, SystemC)
Container.Register(ISystemD, SystemD)
Container.Register(IRepo, MySqlRepo)
Container.Register(myServiceType)

// Do this any time you like
var myService = Container.resolve(myServiceType)

现在,为什么我们没有在许多动态语言中看到 IOC?

我想说的原因是我们在这些语言中没有看到太多的依赖注入。

...那是因为通常在它们中进行的测试是不存在的。

我听过各种各样的借口;与 DOM 交互使测试变得困难,我的代码很简单,不需要测试,动态语言不需要单元测试,因为它们很棒且富有表现力。

都是废话。

没有单元测试或代码覆盖率差的单元测试的项目没有任何借口

...但是我看到的 javascript 和 python 项目的数量令人惊讶(特别选择这两个只是因为它们是一个感兴趣的领域,而且我看到的这种类型的项目比其他项目多)没有IoC,没有 DI,不出所料,没有测试。

这里的 guice 网站上有一篇关于 DI 的优秀文章: http://code.google.com/p/google-guice/wiki/Motivation

动态语言无法解决任何这些问题。

总结:

  • IoC 对事物有用,但主要用于实现 DI
  • IoC 是 NOT xml 配置文件。 >_
  • DI 对测试很有用
  • 没有 IOC 表示没有 DI,这表明没有良好的测试。
  • 使用 IoC。

【讨论】:

  • 是的!至少有人得到它。动态类型不会改变您对对象之间的依赖关系进行硬编码的事实。 DI 背后的整个想法是将您的应用程序作为小部件的组合,所有这些小部件都在您的代码中的一个点组装。这样,很容易添加、删除或交换功能。国际奥委会容器只是使所说的地方看起来整洁甚至不存在。时期。单元测试很快就指出了这一点。这就是为什么大多数人不编写测试。这对任何认真对待测试的人来说都是显而易见的。静态或动态.. 见 Angular
【解决方案4】:

因为它们已经内置在语言中。

IoC 容器提供两件事:

  • 动态绑定
  • 一种动态语言(通常是一种非常糟糕的语言,建立在 XML 之上或在 Java 注释/.NET 属性之上的更新版本中)

动态绑定已经是动态语言的一部分,而动态语言已经是动态语言。因此,一个 IoC 容器根本没有意义:语言已经是一个 IoC 容器。

另一种看待它的方式:IoC 容器允许您做什么?它允许您获取独立的组件并将它们连接到一个应用程序中,而无需任何组件彼此了解任何信息。将独立的部分连接到应用程序中有一个名称:脚本! (这几乎就是脚本的定义。)许多动态语言恰好也擅长脚本,因此它们非常适合作为 IoC 容器。

请注意,我不是在谈论依赖注入或控制反转。 DI 和 IoC 在动态语言中的重要性与在静态语言中的重要性完全相同,原因完全相同。我说的是 IoC 容器和 DI 框架。那些工具是不必要的,设计原则不是。

【讨论】:

  • 你的观点对 Java 这样的语言不是同样有效吗?您可以像使用任何其他语言一样简单地在 Java 中连接 Java 对象。
  • 我真的很想看看这样的例子。
【解决方案5】:

IoC 提供了一种机制来打破当一个对象在另一个类上调用“new”时获得的耦合。

这是对 IoC 的幼稚看法。通常 IoC 也能解决:

  • 依赖解析
  • 自动组件查找和初始化(如果你在 IoC 中使用 'require',就会出现问题)
  • 不仅适用于单例,也适用于动态范围
  • 99.9% 的时间它对开发人员是不可见的
  • 无需 app.config

全文You underestimate the power of IoC

【讨论】:

    【解决方案6】:

    我相信 IoC 容器在大型 JavaScript 应用程序中是必要的。您可以看到一些流行的 JavaScript 框架包含一个 IoC 容器(例如 Angular $injector)。

    我开发了一个名为 InversifyJS 的 IoC 容器,您可以在 http://inversify.io/ 了解更多信息。

    一些 JavaScript IoC 容器声明要注入的依赖项如下:

    import Katana from "./entitites/katana";
    import Shuriken from "./entitites/shuriken";
    
    @inject(Katana, Shuriken) // Wrong as Ninja is aware of Katana and Shuriken!
    class Ninja {
      constructor(katana: IKatana, shuriken: IShuriken) {
        // ...
    

    这种方法的好处是没有字符串文字。不好的是,我们的目标是实现解耦,我们只是在声明 Ninja 的文件中添加了硬编码对 Katana 和 Shuriken 的引用,这并不是真正的解耦。

    InversifyJS 为您提供真正的解耦。 ninja.js 文件永远不会指向 katana 或 shuriken 文件。但是,它将指向接口(在设计时)或字符串文字(在运行时),这是可接受的,因为这些是抽象,depending upon abstractions 是 DI 的全部内容。

    import * as TYPES from "./constants/types";
    
    @inject(TYPES.IKATANA, TYPES.ISHURIKEN) // Right as Ninja is aware of abstractions of Katana and Shuriken!
    class Ninja { 
      constructor(katana: IKatana, shuriken: IShuriken) {
        // ...
    

    InversifyJS 内核是应用程序中唯一知道生命周期和依赖关系的元素。我们建议在名为 inversify.config.ts 的文件中执行此操作,并将该文件存储在包含应用程序源代码的根文件夹中:

    import * as TYPES from "./constants/types";
    import Katana from "./entitites/katana";
    import Shuriken from "./entitites/shuriken";
    import Ninja from "./entitites/ninja";
    
    kernel.bind<IKatana>(TYPES.IKATANA).to(Katana);
    kernel.bind<IShuriken>(TYPES.ISHURIKEN).to(Shuriken);
    kernel.bind<INinja>(TYPES.ININJA).to(Ninja);
    

    这意味着应用程序中的所有耦合都发生在一个独特的位置inversify.config.ts 文件。这非常重要,我们将通过一个例子来证明这一点。假设我们正在更改游戏中的难度。我们只需要转到inversify.config.ts 并更改 Katana 绑定即可:

    import Katana from "./entitites/SharpKatana";
    
    if(difficulty === "hard") {
        kernel.bind<IKatana>(TYPES.IKATANA).to(SharpKatana);
    } else {
        kernel.bind<IKatana>(TYPES.IKATANA).to(Katana);
    }
    

    您无需更改 Ninja 文件!

    要付出的代价是字符串文字,但如果您在包含常量的文件中声明所有字符串文字 (like actions in Redux),则可以减轻此代价。好消息是,将来字符串文字 could end up being generated by the TS compiler,但目前由 TC39 委员会掌握。

    您可以在线试用here

    【讨论】:

      【解决方案7】:

      IOC 容器的主要功能之一是您可以在运行时自动将模块“连接”在一起。在动态语言中,您可以相当容易地做到这一点,而无需任何花哨的基于反射的逻辑。然而,IOC 容器是一种很多人都理解的有用模式,使用相同的设计风格有时可能会有一些好处。其他观点见this article

      【讨论】:

        【解决方案8】:

        IoC 容器确实允许使用静态类型、过程/OO 语言的组合层。

        这种组合层相对自然地存在于 Python 或 Javascript 等动态语言中(考虑到 Javascript 很大程度上基于 Scheme)。

        您可能会提出一个很好的论点,即 IoC 容器只是解释器模式的概括。

        【讨论】:

        • 为什么?这似乎更像是一个论点的断言而不是一个理由。 angular 有类似 IoC 容器的东西,不是吗?
        【解决方案9】:

        Herding Code 82 (6/6/10) 将 Ruby 与 .NET 进行了比较,并详细讨论了 .NET 在多大程度上需要比 Ruby 更多的 IOC/DI。

        【讨论】:

          猜你喜欢
          • 2011-03-05
          • 2014-04-29
          • 2019-01-24
          • 2019-02-19
          • 2011-05-05
          • 2020-08-02
          • 2017-08-22
          • 2010-09-18
          • 1970-01-01
          相关资源
          最近更新 更多