44

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

4

9 回答 9

51

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

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

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

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

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

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

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

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

于 2010-02-22T02:36:27.840 回答
27

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

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

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

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

于 2010-02-22T03:00:19.430 回答
19

我同意上面的答案,但我想我也会在这里加入一些关于测试的信息:

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

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

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

应该有两种测试:

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

现在问题来了:国际奥委会。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不是xml 配置文件。>_<
  • DI 对测试很有用
  • 没有 IOC 表明没有 DI,这表明没有良好的测试。
  • 使用国际奥委会。
于 2011-12-08T02:27:36.327 回答
17

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

IoC 容器提供了两件事:

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

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

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

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

于 2010-02-16T19:41:08.427 回答
4

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

这是对 IoC 的幼稚看法。通常 IoC 还可以解决:

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

完整文章你低估了 IoC 的力量

于 2011-06-26T15:48:04.190 回答
4

我相信 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 文件。但是,它将指向可接受的接口(在设计时)或字符串文字(在运行时),因为这些是抽象,而依赖于抽象是 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 文件!

要付出的代价是字符串文字,但如果您在包含常量的文件中声明所有字符串文字(如 Redux 中的操作) ,则可以减轻此代价。好消息是,将来字符串文字可能最终由 TS 编译器生成,但目前这在 TC39 委员会手中。

你可以在这里在线尝试。

于 2016-03-13T20:39:12.897 回答
3

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

于 2010-02-16T14:52:39.130 回答
3

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

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

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

于 2010-02-22T02:45:39.303 回答
0

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

于 2010-06-07T13:34:20.050 回答