Wednesday, March 19, 2008

IoC and DI

Inversion of Control Containers and Dependency Injection.

A coworker of mine gave me an MSDN article called Loosen Up which goes over Dependency Inversion, Dependency Injection, Inversion of Control containers, and 3rd party IoC tools. It's an excellent article, I highly recommend it.

In an early post I talked about Dependency Injection and referred to it as a slippery slope. I still feel this way to some extent, but its the only way to do good unit testing, so we're stuck with it.

First I want to point out something obvious I got from that article that I hadn't thought of somehow. In Dependency Injection I mentioned how my classes defaulted their dependencies in the constructor and used Properties to allow different instances to be passed in.

This sucks for two reasons:
  1. If someone does actually pass in a different instance of a dependency, the default will have been newed up for nothing
  2. There are ton of properties on the object that it is likely no one will ever use
What's the obvious way to fix this? Create a constructor that takes in the dependencies. Create a default constructor that calls the other constructor:
public LessStupidClass( depA, depB, depC ) {...}
public LessStupidClass() : this( new depA(), new depB(), new depC() ) {...}
Solves both the problems of the stupid way I was doing it.

On to IoC. The idea behind Inversion of Control Containers is basically that you don't like the fact that the class has a way of specifying its default dependencies. You'd rather have the class lookup what its defaults should be in a "Container of Default Dependencies" that has been pre-configured. This can easily be done by performing the lookup based on the interface type of the dependency. The third party tools (like Spring.NET and Castle Windsor) take this even further and allow you to setup the default dependencies in configuration files.

That's the idea at least. Personally, I'm not a fan. I can see how this might be useful if you actually anticipated swapping in different dependencies frequently, or even at all. However, I'm just doing it so I can unit test. So introducing this container layer just makes it harder for me to figure out what the hell is going on in my code. Its bad enough depending on an interface because I can't "right click -> go to definition" any more. And it only gets worse when you move it into a configuration file...

Maybe I'm missing something here. If so, please let me know. But as far as I can tell, for the ways in which I've been using dependency injection, in which there is only 1 concrete dependency class and there is likely only going to be one, EVER, IoC just adds unneeded complexity. It moves the stuff I need to know further away from the places where I need to know it.

And it "centralizes" my dependencies... In my experience, "centralizing" things is only good when it makes sense that everyone share from the same source. It doesn't make sense to centralize when you're either A) forcing things to match that otherwise wouldn't have to match or B) moving things away from where they are used just so they can be "centered." Centralizing also has the unwanted side effect of causing things to get all tangled together. Now that everyone is sharing the same thing, defined in the same place, I have to be really careful about changing that thing... Because if I change it, it will change everywhere, even if I didn't know everywhere it was used.

Aside: Sometimes you want things to be centralized. But make sure you REALLY want it before doing it. Like consistency, it's not a good idea in and of itself.

UPDATE 6/27/08: for more on complexity, read this followup post

2 comments:

  1. You want the interfaces for testability (unless you want to buy TypeMock).

    But, to get the real code, quickly and easily for an interface-based reference: buy ReSharper. After that, you hit Ctrl-Shift-B and it will bring you right to the implementation.

    ReplyDelete
  2. Thanks for the comment!

    I actually do use ReSharper. The point I was trying to make was more that IoC introduces more indirection (and therefore complexity) for the sake of testing alone. I was just using the "right click -> go to definition" as an example.

    ReplyDelete