关于第68号羊群编号播客http://herdingcode.com/herding-code-68-new-year-shenanigans/的人,声称IOC容器没有使用Python或Javascript的地方,或者就此而言。我认为这是传统智慧,它适用于所有动态语言。为什么?什么是动态语言,使IOC容器不必要?
答案 0 :(得分:46)
IoC提供了一种机制来打破当对象在另一个类上调用“new”时所获得的耦合。这种耦合将调用对象与它实现的任何接口的实例化实现联系起来。
在静态语言中,当您按名称引用类(在其上调用new
)时,没有歧义。这是对特定类的紧密耦合。
在动态语言中,调用new X
是一个占位符,用于“在执行时实例化任何类被定义为X
”。这是更松散的耦合,因为它只与名称X
耦合。
这种微妙的差异意味着在动态语言中你通常可以改变X
是什么,所以关于实例化哪个类的决定仍然可以在调用类之外修改。
然而,我个人发现IoC有两个优点,我依靠动态语言来实现注入。
通过构造函数传递依赖项的一个副作用是最终得到的“构建块”类非常分离,可重用且易于测试。他们不知道他们打算使用哪种上下文,因此您可以在所有地方重复使用它们。
另一个结果是有明确的代码来进行布线。正确完成这完全代表了应用程序的结构,它分解为子系统和生命周期。这使得人们明确地决定他们想要将他们的类关联到哪个生命周期或子系统(在编写布线代码时),并在编写类时专注于对象的行为。
就像JörgWMittag所说.. “这些工具是不必要的,设计原则不是。”我认为它们是不必要的,但是做得对,仍然很有价值。
答案 1 :(得分:24)
我有不同的意见。我认为IOC容器肯定在动态语言中起作用。
我不同意这样一种观点,即一种动态的语言不需要一个结构清晰的对象组合。或者动态语言“提供”相同的功能。
IOC容器只是管理该组织的工具。
即使是动态语言,我也希望将组件“连接”起来。没有在这些组件之间建立硬依赖关系。或者甚至可能没有为这些组件指定实际的实现类。
答案 2 :(得分:20)
我同意上面的答案,但我认为我在这里也会考虑一下测试:
在子系统之间存在交互的复杂系统中,依赖注入是我所知道的进行单元测试的最佳方式。
如果你有一个与逻辑单元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
没有任何动态语言可以解决任何这些问题。
要点:
答案 3 :(得分:16)
因为它们已经内置于语言中。
IoC容器提供两件事:
动态绑定已经是动态语言的一部分,动态语言已经是一种动态语言。因此,IoC容器根本没有意义:该语言已经是IoC容器。
另一种看待它的方法:IoC容器允许你做什么?它允许您将独立组件连接在一起并将它们连接到一个应用程序中,而不需要任何组件相互了解。在应用程序中有一个连接独立部分的名称:脚本! (这几乎就是脚本的定义。)许多动态语言在脚本编写方面也相当不错,因此它们非常适合作为IoC容器。
请注意,我不谈论依赖注入或控制反转。 DI和IoC在动态语言中只是 和静态语言一样重要,原因完全相同。我所说的是IoC容器和DI框架。那些工具是不必要的,设计原则不是。
答案 4 :(得分:4)
IoC提供了一种机制来打破当对象在另一个类上调用“new”时所获得的耦合。
这是对IoC的天真看法。通常IoC也解决了:
答案 5 :(得分: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) {
// ...
这种方法的好处是没有字符串文字。关于它的坏处是我们的目标是实现解耦,我们只是将一个硬编码的对Katana和Shuriken 的引用添加到声明Ninja的文件中,这不是真正的解耦。
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);
}
您无需更改忍者文件!
要支付的价格是字符串文字,但如果在包含常量(like actions in Redux)的文件中声明所有字符串文字,则可以减轻此价格。好消息是,在未来的字符串文字could end up being generated by the TS compiler,但目前掌握在TC39委员会手中。
您可以在线试用here。
答案 6 :(得分:3)
IOC容器的一个主要功能是您可以在运行时自动将模块“连接”在一起。在动态语言中,您可以相当轻松地完成此操作,而无需任何基于反射的逻辑。但是,IOC容器是许多人理解的有用模式,使用相同的设计风格有时可能会带来一些好处。有关其他观点,请参阅this article。
答案 7 :(得分:2)
IoC容器确实允许静态类型的过程/ OO语言中的组合层。
这个组合层相对自然地存在于动态语言中,例如Python或Javascript(考虑到Javascript主要基于Scheme)。
你可能会提出一个很好的论据,即IoC容器只是解释器模式的概括。
答案 8 :(得分:0)
Herding Code 82(6/6/10)将Ruby与.NET进行了比较,并详细讨论了.NET需要比Ruby更多IOC / DI的程度。