为什么动态语言不需要 IOC 容器
Herding Code 播客第 68 期,http://herdingcode.com /herding-code-68-new-year-shenanigans/ 指出 IOC 容器不适合使用 Python 或 Javascript,或者类似的词语。我假设这是传统观点,并且它适用于所有动态语言。为什么?动态语言的哪些特点使得 IOC 容器变得不必要?
Someone on the Herding Code podcast No. 68, http://herdingcode.com/herding-code-68-new-year-shenanigans/, stated that IOC containers had no place with Python or Javascript, or words to that effect. I'm assuming this is conventional wisdom and that it applies to all dynamic languages. Why? What is it about dynamic languages that makes IOC containers unnecessary?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(9)
IoC 提供了一种机制来打破当一个对象在另一个类上调用“new”时所获得的耦合。这种耦合将调用对象与其实现的任何接口的实例化实现联系起来。
在静态语言中,当您按名称引用类(对其调用
new
)时,不会产生歧义。这是与特定类的紧密耦合。在动态语言中,调用
new X
是“在执行时实例化定义为X
的任何类”的占位符。这是一个松散耦合,因为它仅与名称X
耦合。这种微妙的差异意味着,在动态语言中,您通常可以更改
X
的含义,因此实例化哪个类的决定仍然可以在调用类之外进行修改。然而,我个人发现 IoC 有两个优点是我依靠动态语言允许注入所无法获得的。
通过构造函数传递依赖项的副作用之一是,您最终会得到非常解耦、可重用且易于测试的“构建块”类。他们不知道要在什么环境中使用它们,因此您可以在任何地方重复使用它们。
另一个结果是有明确的代码来进行连接。如果做得正确,这可以清晰地代表应用程序的结构,并将其分解为子系统和生命周期。这使得人们明确地决定他们想要将他们的类与哪个生命周期或子系统关联起来(在编写连接代码时),并在编写类时集中精力于对象的行为。
就像 Jörg W Mittag 所说......“这些工具是不必要的,设计原则则不是。”我相信它们是不必要的,但如果做得正确,仍然有价值。
IoC provides a mechanism to break the coupling you get when an object calls 'new' on another class. This coupling ties the calling object with the instantiated implementation of whatever interface it implements.
In static languages when you reference a class by name (to call
new
on it), there is no ambiguity. This is a tight coupling to a specific class.In dynamic languages calling
new X
is a placeholder for "instantiate whatever class is defined asX
at the point of execution". This is a looser coupling, as it's only coupled to the nameX
.This subtle difference means that in a dynamic language you can usually change what
X
is, so the decision as to which class is instantiated is still modifiable outside of the calling class.However, personally I find there are two advantages to IoC that I don't get by relying on the dynamic language to allow injection.
One of the side effects of passing dependencies in through constructors is that you end up with "building block" classes that are very decoupled, reusable and easy to test. They have no idea what context they are intended to be used in, so you can reuse them all over the place.
The other result is having explicit code to do the wiring. Done correctly this cleanly represents the structure of your application and it's decomposition into subsystems and life-cycles. This makes people explicitly decide which life-cycle or subsystem they want to associate their class with (when writing the wiring code), and concentrate on the behavior of the object when writing the class.
Like Jörg W Mittag said.. "Those tools are unnecessary, the design principles aren't." I believe they are unnecessary, but done right, still valuable.
我有不同的看法。我认为 IOC 容器在动态语言中肯定发挥着作用。
我不同意这样的观点:动态语言消除了对对象的结构清晰的组合的需要。或者动态语言“提供”相同的功能。
IOC 容器只是管理该组织的工具。
即使在动态语言中,我也想将组件“连接”在一起。无需在这些组件之间建立硬依赖关系。或者甚至可能没有指定这些组件的实际实现类。
I have a different opinion. I think IOC containers certainly have a role in dynamic languages.
I do not share the opinion that a language being dynamic removes the need for a clearly structured composition of objects. Or that a dynamic language 'provides' the same functionality.
An IOC container is simply a tool to manage this organization.
Even in a dynamic language I want to 'wire' together components. Without making hard dependencies between those components. Or maybe even without specifying the actual implementation class for those components.
我同意上面的答案,但我想我也应该在这里讨论一下关于测试的问题:
在子系统之间存在交互的复杂系统中,依赖注入是我所知道的进行单元测试的最佳方式。
如果您有一个逻辑单元 X,它与逻辑单元 Y 具有已知的交互,您可以创建一个具有预定义行为的 MockY 并显式测试 X 的逻辑。
如果没有依赖注入,编写测试就是一场噩梦。您无法获得良好的代码覆盖率。一些框架(例如 django)通过启动模拟数据库实例来进行测试等来解决这个问题,但这基本上是一个糟糕的问题解决方案。
应该有两种类型的测试:
现在回答问题:国际奥委会。 IoC 有什么用?它在某些方面很方便,但它确实非常适合使依赖注入变得更容易:
进入这个逻辑:
现在,为什么我们在许多动态语言中看不到 IOC?
我想说原因是我们在这些语言中没有看到太多依赖注入。
...那是因为通常在其中进行的测试是不存在的。
我听过各种各样的借口;与 DOM 交互使测试变得困难,我的代码足够简单,不需要测试,动态语言不需要单元测试,因为它们非常棒且富有表现力。
都是废话。
对于没有单元测试或代码覆盖率较差的单元测试的项目来说,没有任何借口。
...但是我见过的 javascript 和 python 项目的数量令人惊讶(专门选择这两个项目只是因为它们是一个感兴趣的领域,而且我见过的此类项目比其他项目更多),没有 IoC,没有DI,不出所料,没有测试。
guice 网站上有一篇关于 DI 的优秀文章:
http://code.google.com/p/google-guice/wiki/Motivation 动态语言无法解决
这些问题。
摘要:
I agree with the answers above, but I thought I'd chip in a little bit here too regarding testing:
In complex systems where there are interactions between sub-systems dependency injection is the best way that I'm aware of to do unit testing.
If you have a logic unit X, that has known interactions with logic unit Y, you can create a MockY that has a predefined behaviour and explicitly test the logic of X.
Without dependency injection, writing tests is a nightmare. You cannot get good code coverage. Some frameworks (eg. django) work around this problem by spinning up mock database instances to talk to for tests, etc. but it's basically a poor solution to the problem.
There should be two sorts of tests:
Now to the question: IoC. What is IoC good for? It's handy for a few things, but its really really good for making it easier to use dependency injection:
Into this logic:
Now, why don't we see IOC in many dynamic languages?
I'd say the reason is that we don't see much dependency injection in those languages.
...and that would be because typically the testing done in them is non-existent.
I've heard all kinds of excuses for this; interacting with the DOM makes tests hard, my code is simple enough it doesn't require tests, dynamic languages don't need unit tests because they're awesome and expressive.
It's all nonsense.
There is no excuse for a project without unit tests or unit tests with poor code coverage.
...but it's amazing the number of javascript and python projects I've seen (picking on these two specifically only because they're an area of interest and I've seen more projects of this type than others) with no IoC, no DI, and unsurprisingly, no tests.
There is an excellent article on DI on the guice website here:
http://code.google.com/p/google-guice/wiki/Motivation
There is nothing about dynamic languages that solves any of these problems.
Summary:
因为它们已经内置于语言中。
IoC 容器提供两件事:
动态绑定已经是动态语言和动态语言的一部分语言已经是一种动态语言。因此,IoC 容器根本没有意义:该语言已经是一个 IoC 容器。
另一种看待它的方式:IoC 容器允许你做什么?它允许您采用独立的组件并将它们连接到一个应用程序中,而无需任何组件相互了解。将独立的部分连接到应用程序中有一个名称:脚本! (这几乎就是脚本的定义。)许多动态语言恰好也非常擅长脚本,因此它们非常适合作为 IoC 容器。
请注意,我不是在谈论依赖注入或控制反转。出于完全相同的原因,DI 和 IoC 在动态语言中与在静态语言中同样重要。我说的是IoC容器和DI框架。这些工具是不必要的,设计原则则不是。
Because they are already built into the language.
An IoC container provides two things:
Dynamic binding is already part of the dynamic language and the dynamic language is already a dynamic language. Therefore, an IoC container simply doesn't make sense: the language is already an IoC container.
Another way to look at it: what is it that an IoC container allows you to do? It allows you to take independent components and wire them up together into an application, without any of the components knowing anything about each other. There is a name for wiring independent pieces together into an application: scripting! (That's pretty much the definition of scripting.) Many dynamic languages happen to also be pretty good at scripting, therefore they are perfect as IoC containers.
Please note that I am not talking about Dependency Injection or Inversion of Control. DI and IoC are just as important in dynamic languages as they are in static languages, for exactly the same reasons. What I am talking about are IoC containers and DI frameworks. Those tools are unnecessary, the design principles aren't.
这是对 IoC 的幼稚看法。通常 IoC 还可以解决:
全文你低估了 IoC 的力量
It's naive view on IoC. Usually IoC also solves:
full article You underestimate the power of IoC
我相信 IoC 容器在大型 JavaScript 应用程序中是必要的。您可以看到一些流行的 JavaScript 框架包含 IoC 容器(例如 Angular
$injector
)。我开发了一个名为 InversifyJS 的 IoC 容器,您可以在 http://inversify.io/ 了解更多信息。
一些 JavaScript IoC 容器声明要注入的依赖项,如下所示:
这种方法的好处是没有字符串文字。不好的是,我们的目标是实现解耦,而我们只是在声明 Ninja 的文件中添加了对 Katana 和 Shuriken 的硬编码引用,这并不是真正的解耦。
InversifyJS 为您提供真正的解耦。 ninja.js 文件永远不会指向 katana 或 shuriken 文件。但是,它将指向可接受的接口(在设计时)或字符串文字(在运行时),因为这些是抽象,并且 依赖于抽象就是 DI 的全部内容。
InversifyJS 内核是应用程序中唯一了解生命周期和依赖关系的元素。我们建议在名为
inversify.config.ts
的文件中执行此操作,并将该文件存储在包含应用程序源代码的根文件夹中:这意味着应用程序中的所有耦合都会发生在一个独特的地方:
inversify.config.ts
文件。这非常重要,我们将用一个例子来证明这一点。让我们想象一下,我们正在改变游戏的难度。我们只需要转到inversify.config.ts
并更改 Katana 绑定:您不需要更改 Ninja 文件!
付出的代价是字符串文字,但是如果您在包含常量的文件中声明所有字符串文字(类似 Redux 中的操作)。好消息是,将来字符串文字最终可能由 TS 编译器生成,但这目前由 TC39 委员会负责。
您可以在此处在线尝试。
I believe that IoC containers are necessary in large JavaScript applications. You can see that some popular JavaScript frameworks include an IoC container (e.g. the Angular
$injector
).I have develop an IoC container called InversifyJS you can learn more about it at http://inversify.io/.
Some JavaScript IoC containers out there declare the dependencies to be injected as the following:
The nice thing about this approach is that there are no string literals. The bad thing about it is that our goal was to achieve decoupling and we just added a hard coded a reference to Katana and Shuriken to the file in which Ninja is declared and this is not real decoupling.
InversifyJS offers you real decoupling. The ninja.js file will never point to the katana or shuriken files. However, it will point to the interfaces (at design-time) or string literals (at run-time) which is admissible because these are abstractions and depending upon abstractions is what DI is all about.
The InversifyJS kernel is the only element in the application aware of the life-cycle and dependencies. We recommend to do this in a file named
inversify.config.ts
and store the file in the root folder that contains the application source code:This means that all the coupling in your application takes place in one unique place: the
inversify.config.ts
file. This is really important and we are going to prove it with an example. Let's imagine that we are changing the difficulty in a game.We just need to go to theinversify.config.ts
and change the Katana binding:You don't need to change the Ninja file!
The price to pay is the string literals but this price can be mitigated if you declare all your string literals in a file which contains constants (like actions in Redux). The good news is that in the future the string literals could end up being generated by the TS compiler, but that is in the hands of the TC39 committee for the moment.
You can try it online here.
IOC 容器的主要功能之一是您可以在运行时自动将模块“连接”在一起。在动态语言中,您可以相当轻松地完成此操作,无需任何花哨的基于反射的逻辑。然而,IOC 容器是一种很多人都理解的有用模式,有时使用相同的设计风格可能会有一些好处。请参阅这篇文章以获得另一个观点。
One of the main features of IOC containers is that you can automatically "wire" your modules together at runtime. In dynamic languages you can do this fairly easily without any fancy reflection-based logic. However, IOC containers are a useful pattern that many people understand and it may sometimes be of some benefit to use the same design style. See this article for another viewpoint.
IoC 容器确实允许静态类型、过程/面向对象语言的组合层。
这种组合层相对自然地存在于 Python 或 Javascript 等动态语言中(考虑到 Javascript 在很大程度上基于Scheme)。
您可能会提出一个很好的论点:IoC 容器只是解释器模式的泛化。
IoC containers really allow for a compositional layer in statically typed, procedural/OO languages.
This compositional layer exists relatively naturally in dynamic languages such as Python or Javascript (consider that Javascript is heavily based on Scheme).
You could probably make a good argument that IoC containers are just a generalization of the Interpreter pattern.
Herding Code 82 (6/6/10) 将 Ruby 与 .NET 进行了比较,并包含了一些详细讨论.NET 比 Ruby 需要更多 IOC/DI 的程度。
Herding Code 82 (6/6/10) compares Ruby with .NET and includes some detailed discussion on the extent to which .NET needs more IOC/DI than Ruby.