Pages

Monday 4 January 2010

Cheap IoC in native C++

In a 2008 episode of dnrTV James Kovac describes how to create a very simple IoC container. All the container does is mapping the name of an interface to an instance of this interface.

Easy to do in .NET with a generic Dictionary, but what about native C++? It turns out it's not that much more difficult: all you need is a STL map and C++'s typeid. 

What is an IoC container?
A class that given an interface ISomething returns the concrete object implementing ISomething.


Why do you need an IoC container ?

It's one way to do dependency injection.

Let's say you want to unit-test some business layer that relies on a data layer. The business layer sees the data layer through an interface (let's call it IDataLayer). The actual instance of IDataLayer is created by either:

  • the entry point of the code that uses the business layer (an executable for instance). This is production code and it has to create the object connecting to the real database.
 
int _tmain(int argc, _TCHAR* argv[])
{
    // Create the concrete DataLayer object
    DataLayer dataLayer;

    // Register it with the resolver
    Resolver::Register<IDataLayer>(dataLayer);

    BusinessStuff businessStuff;
    businessStuff.DoStuff();

    return 0;
}
  • the unit-test code. The IDataLayer object in that case is a fake object that doesn't connect to the real database and therefore makes unit-tests easily reproducible and fast.

void BusinessStuffTest::DoStuff_Nominal_DoesNotRaiseException()
{
    // Arrange

    // Create the concrete DataLayer object
    FakeDataLayer dataLayer;

    // Register it with the resolver
    Resolver::Register<IDataLayer>(dataLayer);

    BusinessStuff businessStuff;

    // Act
    businessStuff.DoStuff();

    // Assert
    dataLayer.AssertStuffWasDone();

}

    Implementation of the Resolver class
    The Resolver class has two methods Register and Resolve. Here is the code for the Resolver class (very straightforward):


      #pragma once

      #include
      #include

      using namespace std;

      ///
      /// Allows you to Register and Resolve global objects
      ///
      class Resolver
      {
          static map<string, void* > typeInstanceMap;

      public:

          template<class T> static void Register(const T& object)
          {
              typeInstanceMap[typeid(T).name()] = (void*)&object;
          }

          template<class T> static T& Resolve()
          {
              return *((T*)typeInstanceMap[typeid(T).name()]);
          }
      };

      This is a just a starting point. In production Resolver could do with methods such as Unregister(), IsRegistered(), UnregisterAll()...


      An IoC container is one way to do dependency injection. Other ways include:
      • pass the injected object using the constructor
      • pass the injected object using a setter method
      Both methods above can become tedious very quickly in a big project.
      I tried all 3 methods and I find the IoC approach scales rather well. I've used it for 10 months in a production project and it works nicely with unit tests.

      Resources:

      Jean-Paul Boodhoo on the gateway pattern
      Roll your own IoC container (dnrTV)
      The Art of Unit-Testing where Roy Osherove tells everything about dependency injection and fake objects.

      6 comments:

      _ said...

      Check out the code I'm working on for just this very thing: http://bitbucket.org/cheez/dicpp/wiki/Home

      Looks pretty similar to yours but has a bit more features.

      Ke Jin said...

      > It's one way to do dependency injection.

      This is wrong. If your only purpose is to do DI/IoC, you should not use an IoC container. Just like mount a wheel on a bike is not the cheapest way to spin it.

      IoC container isn't to do DI/IoC but merely used the design/principle of IoC. In another words, it is not IoC container facilities DI/IoC, but DI/IoC enables IoC container. This is similar to that old EJB (2.x) containers were not to do or to help service lookup (a hash table would be much cheap than an EJB container then) but simply used service lookup. A bike isn't to spin wheels, it uses wheels....

      Unknown said...
      This comment has been removed by the author.
      Unknown said...

      No, just me being dumb.

      m_dataLayer needs to be a reference, not a pointer and resolver needs the type as the template parameter.

      public:
      BusinessStuff() : m_dataLayer(Resolver::Resolve()) { }
      private:
      IDataLayer& m_dataLayer;

      Matt said...

      Thanks grahamr! I'll check and fix that as soon as I get the chance

      Mark said...

      Inspired by your article I have did my own, see here:
      http://marek77.wordpress.com/2011/09/07/basic-ioc-inversion-of-control-container-for-c/