为什么dynamic语言不需要IOC容器?
有人在“羊群规范”播客第68号http://herdingcode.com/?p=231上说,国际奥委会的容器没有Python或Javascript的地方,或者说是这个词的意思。 我假设这是传统的智慧,它适用于所有的dynamic语言。 为什么? 什么是dynamic语言,使IOC容器不必要?
IoC提供了一种机制来打破当一个对象在另一个类上调用“new”时所获得的耦合。 这种耦合将调用对象与其实现的任何接口的实例化实现联系起来。
在静态语言中,当你通过名字引用一个类(在它上面调用new
)时,没有任何歧义。 这是对特定课程的紧密结合 。
在dynamic语言中调用new X
是一个占位符,用于“在执行时实例化任何定义为X
类”。 这是一个宽松的耦合 ,因为它只与名字X
。
这种细微的差别意味着在dynamic语言中,通常可以改变X
是什么,所以关于实例化哪个类的决定在调用类之外仍然是可修改的。
但是,我个人觉得IoC有两个好处,我不能依靠dynamic语言来实现注入。
通过构造函数传递依赖关系的一个副作用是,最终得到的“构build块”类是非常分离的,可重用的并且易于testing。 他们不知道他们打算使用什么样的环境,所以你可以在整个地方重复使用它们。
另一个结果是有明确的代码做布线。 正确地做了这个干净地表示你的应用程序的结构,它分解成子系统和生命周期。 这使得人们明确地决定他们想把哪个生命周期或者子系统与他们的class级联系起来(在编写布线代码的时候),并且在写class的时候专注于对象的行为。
就像JörgW Mittag说的那样。 “那些工具是不必要的,devise原则不是。” 我相信这是不必要的,但是做得对,还是有价值的。
我有不同的意见。 我认为IOC容器肯定在dynamic语言中起作用。
我不同意这样一种观点,即dynamic语言不需要一个结构清晰的对象组合。 或者说一个dynamic语言“提供”了相同的function。
国际奥委会容器只是一个pipe理这个组织的工具。
即使在dynamic语言中,我也想将各个组件“连线”在一起。 不要在这些组件之间做出硬性依赖关系。 或者甚至可能没有指定这些组件的实际实现类。
我同意上面的答案,但是我认为我也会在这方面对testing进行一点研究:
在子系统之间存在交互的复杂系统中,dependency injection是我知道进行unit testing的最佳方法。
如果你有一个逻辑单元X,它与逻辑单元Y有已知的交互,你可以创build一个具有预定义行为的MockY,并明确地testingX的逻辑。
没有dependency injection,写testing是一场噩梦。 你不能得到很好的代码覆盖率。 一些框架(例如django)通过转换模拟数据库实例来交谈testing等来解决这个问题,但这基本上是一个很糟糕的解决scheme。
应该有两种testing:
- 在任何环境下运行的unit testing,并testing各个代码单元的逻辑。
- 集成/functiontesting,testing组合的应用程序逻辑。
现在来回答这个问题:IoC。 IoC有什么好处? 这对于一些事情是很方便的,但是使它更容易地使用dependency injection是非常好的:
// 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)
现在,为什么我们不能在很多dynamic语言中看到IOC?
我想说的原因是,我们没有看到这些语言的dependency injection。
…那是因为通常他们所做的testing是不存在的。
我听到过各种各样的借口, 与DOM交互使得testing变得困难,我的代码很简单,不需要testing,dynamic语言不需要unit testing,因为它们很棒,而且很有performance力。
这都是无稽之谈。
没有unit testing或者代码覆盖率低的unit testing的项目是没有借口的。
…但是,我所见过的javascript和python项目的数量是惊人的(select这两个具体只是因为它们是一个感兴趣的领域,而且我看到了比其他项目更多的项目),没有IoC DI,毫不奇怪,没有testing。
在guice网站上有一篇关于DI的优秀文章: http : //code.google.com/p/google-guice/wiki/Motivation
dynamic语言没有任何解决这些问题的方法。
概要:
- IoC对事物有用,但主要用于实施DI
- IoC 不是 xmlconfiguration文件。 > _ <
- DI对于testing是有用的
- 缺乏IOC表明没有DI,这表明没有好的testing。
- 使用IoC。
因为他们已经build立在语言。
一个IoC容器提供了两件事情:
- dynamic绑定
- 一个dynamic的语言(通常是一个令人难以置信的糟糕的语言,build立在XML之上或者在Java注释/ .NET属性之上的新版本中)
dynamic绑定已经是dynamic语言的一部分,dynamic语言已经是一种dynamic语言。 因此,IoC容器根本没有意义:语言已经是IoC容器。
另一种看待它的方法是:一个IoC容器允许你做什么? 它可以让你独立的组件,并将它们连接在一起成为一个应用程序,没有任何组件知道任何关于对方。 有一个独立的布线连接到一个应用程序的名字:脚本! (这几乎就是脚本的定义。)许多dynamic语言碰巧在脚本编写上也相当不错,因此它们非常适合作为IoC容器。
请注意,我不是在谈论dependency injection或控制反转。 DI和IoC在dynamic语言中与静态语言一样重要,原因完全相同。 我在说的是IoC容器和DI框架。 那些工具是不必要的, devise原则不是。
IOC容器的主要function之一是可以在运行时自动将模块“连接”在一起。 在dynamic语言中,你可以很容易地做到这一点,没有任何幻想的基于reflection的逻辑。 但是,IOC容器是很多人理解的有用模式,使用相同的devise风格有时会带来一些好处。 看到这篇文章的另一个观点。
IoC提供了一种机制来打破当一个对象在另一个类上调用“new”时获得的耦合。
这是对IoC的天真观点。 通常IoC也解决:
- 依赖关系parsing
- 自动组件查找和初始化(如果您在IoC中使用'require',则出现错误)
- 不仅适用于单身,也适用于dynamic范围
- 99.9%的时间对开发者来说是无形的
- 消除了对app.config的需求
全文你低估了IoC的力量
我相信IoC容器在大型JavaScript应用程序中是必需的。 您可以看到一些stream行的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) { // ...
这种方法的好处是没有string文字。 坏消息是我们的目标是实现解耦,而且我们只是在文本中join了对Katana和Shuriken的硬编码引用,而这个引用并不是真正的解耦。
InversifyJS为您提供真正的解耦。 ninja.js文件永远不会指向katana或shuriken文件。 但是,它将指向可接受的接口(在devise时)或string文字(在运行时),因为这些是抽象的, 取决于抽象是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内核是应用程序意识到生命周期和依赖性的唯一元素。 我们build议在名为inversify.config.ts
的文件中执行此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
文件。 这真的很重要,我们将用一个例子来certificate它。 让我们想象一下,我们正在改变游戏的难度。我们只需要去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); }
你不需要改变忍者文件!
要支付的价格是string文字,但是如果在包含常量的文件( 如Redux中的动作 )中声明所有string文字,此价格可以减轻。 好消息是,将来string文字最终可能由TS编译器生成 ,但目前在TC39委员会手中。
你可以在这里尝试一下 。
IoC容器真的允许在静态types,过程/ OO语言中的组合层。
这个构图层在dynamic语言(比如Python或者Javascript)中相对自然地存在(认为Javascript主要基于Scheme)。
你可能会提出一个很好的论点,即IoC容器只是解释器模式的一个泛化。
Herding Code 82 (6/6/10)比较了Ruby和.NET,并详细讨论了.NET需要比Ruby更多IOC / DI的程度。