Why are IOC containers unnecessary with dynamic languages
Asked Answered
O

9

44

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?

Oddball answered 16/2, 2010 at 14:47 Comment(3)
Watch this video - bestechvideos.com/2008/12/08/… - it's a presentation by the developer of 2 (failed) Ruby IOC projects. TLDR version - "Java is Legos, Ruby is Play-Doh"Pellikka
@Pellikka link is dead. Got another link?Osburn
@Osburn - New link to video is confreaks.tv/videos/rubyconf2008-recovering-from-enterprisePellikka
Q
51

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 as X at the point of execution". This is a looser coupling, as it's only coupled to the name X.

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.

Quag answered 22/2, 2010 at 2:36 Comment(2)
Isn't the question specifically about IoC containers and not the concept of IoC?Insoluble
Brilliant answer. . Regarding tools: The same goes for test frameworks, mocking libraries and so forth. You can get the job done without them, but good tools are invaluable.Breakwater
C
27

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.

Condenser answered 22/2, 2010 at 3:0 Comment(2)
I agree to a point. I believe that even in Dynamic Languages we need to separate wiring from components. I don't believe that IoC Containers are the best way to do this. All you really need is an internal DSL for software wiring. IoC containers as they stand don't exactly fit that description.Quag
IoC containers facilitate certain functionality by providing patterns for accomplishing tasks. Although dynamic languages may contain capabilities that make certain tasks usually accomplished with IoC containers in statically typed languages unnecessary, many of the tasks and patterns are still useful in dynamic languages. See this question for concrete examples.Providenciaprovident
G
20

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:

  • Unit tests that run in ANY environment and test the logic of individual code units.
  • Integration / functional tests that test the combined application logic.

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:

// 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)

Into this logic:

// 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)

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 is useful for things, but primarily for implementing DI
  • IoC is NOT xml config files. >_<
  • DI is useful for tests
  • Absence of IOC is indicative of the absence of DI, which is indicative of the absence of good testing.
  • Use IoC.
Gambell answered 8/12, 2011 at 2:27 Comment(1)
Yes!! At least someone gets it. Dynamic Typing does not change the fact that you are hard coding dependencies between objects. The whole idea behind DI is to have your application as a combination of small parts that all get assembled at one point in your code. That way, it's easy to add, remove or swap functionality. IOC containers just make said place look neat or even non existent. Period. Unit Testing quickly points this out. That's why most people don't write tests. This would be obvious to anyone who takes testing seriously. Static or Dynamic.. See AngularRonni
M
17

Because they are already built into the language.

An IoC container provides two things:

  • dynamic binding
  • a dynamic language (usually an incredibly crappy one, built on top of XML or in newer versions on top of Java annotations/.NET attributes)

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.

Mobcap answered 16/2, 2010 at 19:41 Comment(2)
Aren't your points equally valid for a language like Java? You can wire up Java objects in Java just as easy as with any other language.Insoluble
I would really like to see an example of this.Mortgagor
A
4

IoC provides a mechanism to break the coupling you get when an object calls 'new' on another class.

It's naive view on IoC. Usually IoC also solves:

  • dependency resolving
  • automatic component lookup and initialization (if you use 'require' with IoC, there's something wrong)
  • works not only with singletons but also with dynamic scope
  • 99.9% of time it's invisible for developer
  • eliminates need of app.config

full article You underestimate the power of IoC

Athome answered 26/6, 2011 at 15:48 Comment(0)
B
4

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:

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) {
    // ...

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.

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) {
    // ...

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:

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);

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 the inversify.config.ts and change the Katana binding:

import Katana from "./entitites/SharpKatana";

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

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.

Beeswing answered 13/3, 2016 at 20:39 Comment(0)
C
3

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.

Culbert answered 16/2, 2010 at 14:52 Comment(0)
A
3

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.

Atreus answered 22/2, 2010 at 2:45 Comment(1)
Why? This seems more like an assertion of the argument than a reason. angular has something like an IoC container, doesn't it?Riffle
O
0

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.

Oddball answered 7/6, 2010 at 13:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.