17

I've seen several recommend use of IoC containers in code. The motivation is simple. Take the following dependency injected code:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest(
        std::auto_ptr<Dependency> d = std::auto_ptr<Dependency>(new ConcreteDependency)
    ) : d_(d)
    {
    }
};


TEST(UnitUnderTest, Example)
{
    std::auto_ptr<Dependency> dep(new MockDependency);
    UnitUnderTest uut(dep);
    //Test here
}

Into:

class UnitUnderTest
{
    std::auto_ptr<Dependency> d_;
public:
    UnitUnderTest()
    {
        d_.reset(static_cast<Dependency *>(IocContainer::Get("Dependency")));
    }
};


TEST(UnitUnderTest, Example)
{
    UnitUnderTest uut;
    //Test here
}

//Config for IOC container normally
<Dependency>ConcreteDependency</Dependency>

//Config for IOC container for testing
<Dependency>MockDependency</Dependency>

(The above is hypothetical C++ example of course)

While I agree that this simplifies the interface of the class by removing the dependency constructor parameter, I think the cure is worse than the disease for a couple of reasons. First, and this is a big one for me, this makes your program dependent on an external configuration file. If you need single binary deployment, you simply cannot use these kinds of containers. The second issue is that the API is now weakly and worse, stringly typed. The evidence (in this hypothetical example) is the string argument to the IoC container, and the cast on the result.

So.. are there other benefits of using these kinds of containers or do I just disagree with those recommending the containers?

Billy ONeal
  • 8,073
  • 6
  • 43
  • 57
  • 1
    The sample code looks like a really bad example of an IoC implementation. I'm only familiar with C# but surely there is a way to have constructor injection and programmatical configuration in C++ as well? – rmac Sep 03 '11 at 13:19

3 Answers3

12

In a large application with many layers and lots of moving parts, it's the drawbacks that start to look pretty minor in comparison to the advantages.

The container does "simplify the interface" of the class, but it does so in a very important way. The container is a solution to the problem that dependency injection creates, which is the need to pass dependencies all over the place, down through object graphs and across functional areas. You have one small example here that has one dependency - what if this object had three dependencies, and the objects that depended on it had multiple objects that depended on them, and so on? Without a container, the objects at the top of those dependency chains end up becoming responsible for keeping track of all of the dependencies in the entire application.

There are different kinds of containers, as well. Not all of them are stringly typed, and not all of them require configuration files.

Robert Harvey
  • 198,589
  • 55
  • 464
  • 673
nlawalker
  • 3,002
  • 20
  • 21
  • 3
    But a large project makes the problems I already mentioned *worse*. If the project is large, the configuration file becomes a huge dependency magnet itself, and it becomes even easier to have one typo lead to undefined behavior at runtime. (i.e. having a typo in the config file would cause the cast to cause undefined behavior if the wrong type gets returned). As for dependencies of dependencies, those don't matter here. Either the constructor takes care of making them, or the test is going to be mocking out the top level dependency. – Billy ONeal Feb 22 '11 at 18:06
  • ... Continued ... When the program isn't being tested, the default concrete dependency gets instantiated at each step. W.r.t. other kinds of containers, do you have some examples? – Billy ONeal Feb 22 '11 at 18:07
  • 4
    TBH I don't have tons of experience with DI implementations, but take a look at MEF on .NET, which can be but doesn't have to be stringly typed, has no config file, and concrete implementations of interfaces are "registered" on the classes itself. BTW, when you say: "the constructor takes care of making them" - if that's what you're doing (aka "poor man's constructor injection"), then you don't need a container. The advantage of a container over doing that is that the container centralizes information about all those dependencies. – nlawalker Feb 22 '11 at 21:01
  • +1 for that last comment -- the last sentence there is the answer :) – Billy ONeal Feb 23 '11 at 02:17
  • Note -- I'm checkmarking this answer, but I'm not checkmarking it for the content of the answer (because that I think is drivel). However, the comment two comments above this one is an excellent answer. – Billy ONeal Apr 01 '11 at 02:54
  • It's important to note that there are a couple of disadvantages to building your dependencies in the constructor of the object that uses them: 1) You have no direct control over the lifetime of the object, and 2) If the dependency is in another module (assembly, etc.), you end up with a module dependency. With a container, you can easily enforce/use concepts like lazy instantiation, pooling, and singleton, and all module dependencies are funneled through the container's module. – nlawalker Aug 08 '12 at 19:40
  • I don't see why you don't have control over the lifetime of the dependency. In garbage collected environments, you never have control over dependency lifetimes anyway, and in native environments, that's what `std::unique_ptr` and `std::auto_ptr` and `std::shared_ptr` are for -- they indicate what ownership semantics you want. – Billy ONeal Aug 08 '12 at 20:16
  • I don't know enough about native environments to comment on what you've written there, but "in garbage collected environments, you never have control over dependency lifetimes anyway" is only true if the dependent is constructing the dependency itself. If the dependency is passed in, the object that did the passing can optionally maintain a reference to it - this is what a container would do if you registered a type with a lifetime policy of something other than "create a new instance every time I need an instance." – nlawalker Aug 08 '12 at 21:09
  • Yes, the calling code can keep a reference around -- but there's no way to ever say "destroy this reference" explicitly. IoC containers don't change that rule – Billy ONeal Aug 08 '12 at 21:44
  • Absolutely. I think maybe instead of saying "controlling lifetimes" I should have said "enable instance sharing" - my point was that a container could be used to enable pooling, singletons, etc. – nlawalker Aug 08 '12 at 21:58
  • 1
    Great answer that helped me grok the whole point. The point isn't just simplifying the boilerplate, but allowing the elements at the top of the dependency chain to be just as naïve as the elements at the bottom. – Christopher Berman Mar 25 '16 at 17:50
4

The Guice IoC framework from Java do not rely on a configuration file but on configuration code. This means that the configuration is code no different from the code making up your actual application, and can be refactored etc.

I believe that Guice is the Java IoC framework which finally got configuration right.

0

This great SO answer by Ben Scheirman details some code examples in C#; some advantages of IoC containers (DI) being:

  • Your dependencies chain can become nested, and it quickly becomes unwieldy to wire them up manually.
  • Enables use of Aspect oriented programming.
  • Declarative & nested database transactions.
  • Declarative & nested Unit of work.
  • Logging.
  • Pre/Post conditions (Design by Contract).
dodgy_coder
  • 1,098
  • 7
  • 22
  • 1
    I don't see how any of those things can't be implemented with simple constructor injection. – Billy ONeal Apr 17 '12 at 17:59
  • With a good design, constructor injection or another method may be enough for you; the advantages of these IoC containers might be only in specific cases. The example given with INotifyPropertyChanged in a WPF project was one such example. – dodgy_coder Apr 18 '12 at 00:54