logo

Wallaroo

C++ Dependency Injection

Tutorial

From wikipedia:

The primary purpose of the dependency injection pattern is to allow selection among multiple implementations of a given dependency interface at runtime, or via configuration files, instead of at compile time. The pattern is particularly useful for providing "mock" test implementations of complex components when testing but is often used for locating plugin components, or locating and initializing software services.

Motivation

Let's say you're coding a beautiful application that controls a sump pump:

After a careful design phase (see this blog post to have an accurate description of this phase), you come up with a SumpPump class:

class SumpPump
{
public:
  ...
  void Drain()
  {
    if ( probe -> MustDrain() )
      engine -> On();
    else
      engine -> Off();
  }
private:
  PumpEngine* engine;
  SumpProbe* probe;
};

Let's say you now have a new requirement:

to prevent explosions, the pump must not be operated when methane is above a certain level.

No problem: you're using OO. You can subclass PumpEngine and change the code this way:

class SafeEngine : public PumpEngine
{
public:
  virtual void On()
  {
    if ( ! sensor -> IsCritical() )
      PumpEngine::On();
  }
private:
  GasSensor* sensor;
};


class SumpPump
{
public:
  void Drain()
  {
    if ( probe -> MustDrain() )
      engine -> On();
    else
      engine -> Off();
  }
private:
  PumpEngine* engine;
  SumpProbe* probe;
};

However, somewhere in the code, you must tell the class SumpPump to create an instance of SafeEngine instead of PumpEngine. There are two solutions:

The two alternatives are shown below:
class SumpPump
{
public:
  SumpPump( GasSensor* sensor )
  {
    // engine = new PumpEngine();
    engine = new SafeEngine( sensor );
    probe = new SumpProbe();
  }
...
};
class SumpPump
{
public:
  SumpPump( PumpEngine* pe, SumpProbe* sp ) :
    engine( pe ),
    probe( sp )
  {
  }
...
};

Both the solutions have problems.

In the first, you have to modify the code of SumpPump if you want to use a different engine. Besides, you need to provide the SafeEngine constructor parameters to the SumpPump constructor (and so, you have to modify also the code that creates SumpPump instances).

The second solution is better: you don't change SumpPump code, yet you can't swap the behavior without recompiling your application.

Enter Wallaroo

Instead, I really would like to be able to change the type of the classes without modifying them and without propagating dependencies in the whole class tree.

Let's say I want the freedom to choose late the concrete classes of probe and engine dependencies in SumpPump.

Using Wallaroo we can write the classes this way:

// sumppump.h

class SumpPump : public Part
{
public:
  SumpPump();
  virtual void Drain();
private:
  Collaborator< SumpProbe > probe;
  Collaborator< PumpEngine > engine;
};

and

// sumppump.cpp

WALLAROO_REGISTER( SumpPump )

SumpPump::SumpPump() :
  probe( "probe", RegistrationToken() ),
  engine( "engine", RegistrationToken() )
{
}

void SumpPump::Drain()
{
    if ( probe -> MustDrain() ) engine -> On();
    else engine -> Off();
}

Then, let's say I want to use TwoLevelSumpProbe and SafeEngine as concrete classes of probe and engine, respectively.

Now, the wiring becomes a piece of cake: it can be performed with a DSL in the main (or wherever you want):

Catalog catalog;

catalog.Create( "twoLevelsProbe", "TwoLevelSumpProbe" );
catalog.Create( "safeEngine", "SafeEngine" );
catalog.Create( "pump", "SumpPump" );

wallaroo_within( catalog )
{
    use( "twoLevelsProbe" ).as( "probe" ).of( "pump" );
    use( "safeEngine" ).as( "engine" ).of( "pump" );
}

Otherwise, you can decide to get the objects and perform the wiring loading a configuration file.

Catalog catalog;
XmlConfiguration file( "wiring.xml" );
file.Fill( catalog );
catalog.CheckWiring(); // throws a WiringError exception if any collaborator is missed

the xml file should have this syntax (see here for a detailed description):

<wallaroo>

  <parts>

    <part>
      <name>twoLevelsProbe</name>
      <class>TwoLevelSumpProbe</class>
    </part>

    <part>
      <name>safeEngine</name>
      <class>SafeEngine</class>
    </part>

    <part>
      <name>pump</name>
      <class>SumpPump</class>
    </part>

  </parts>

  <wiring>

    <wire>
      <source>pump</source>
      <dest>twoLevelsProbe</dest>
      <collaborator>probe</collaborator>
    </wire>

    <wire>
      <source>pump</source>
      <dest>safeEngine</dest>
      <collaborator>engine</collaborator>
    </wire>

  </wiring>

</wallaroo>

(If you think the xml file is a bit too verbose you should use wal format instead)

You can also decide to put your classes in shared libraries. Let's say you wanna put SafeEngine class in a .dll or .so. You just substitute the macro WALLAROO_REGISTER with WALLAROO_DYNLIB_REGISTER in this way:

// safeengine.cpp

#include "pumpengine.h"
#include "wallaroo/dynamic_lib.h" // once per library
#include "wallaroo/dyn_registered.h" // once per translation unit

class SafeEngine : public PumpEngine
{
public:
  virtual void On()
  {
    if ( ! sensor -> IsCritical() )
      PumpEngine::On();
  }
private:
  Collaborator< GasSensor > sensor;
};

WALLAROO_DYNLIB_REGISTER( SafeEngine )

Then, you should load the shared library before create your objects:

Plugin::Load( "safeengine" + Plugin::Suffix() ); // load classes in shared libraries.
// Plugin::Suffix() expands to .dll or .so according to the OS
Catalog catalog;
XmlConfiguration file( "wiring.xml" );
file.Fill( catalog );
catalog.CheckWiring(); // throws a WiringError exception if any collaborator is missing

The shared library can also be specified in the xml configuration file:

<wallaroo>

  <plugins>
    <shared>safeengine</shared>
  </plugins>

  <parts>

    <part>
      <name>twoLevelsProbe</name>
      <class>TwoLevelSumpProbe</class>
    </part>

    <part>
      <name>safeEngine</name>
      <class>SafeEngine</class>
    </part>

    <part>
      <name>pump</name>
      <class>SumpPump</class>
    </part>

  </parts>

  <wiring>

    <wire>
      <source>pump</source>
      <dest>twoLevelsProbe</dest>
      <collaborator>probe</collaborator>
    </wire>

    <wire>
      <source>pump</source>
      <dest>safeEngine</dest>
      <collaborator>engine</collaborator>
    </wire>

  </wiring>

</wallaroo>

In this case, you should load the shared libraries in this way:

Catalog catalog;
XmlConfiguration file( "wiring.xml" );
file.LoadPlugins(); // load the shared libraries specified in the configuration file
file.Fill( catalog );
catalog.CheckWiring(); // throws a WiringError exception if any collaborator is missing

Finally, you can get the entry point and start the main loop:

shared_ptr< SumpPump > pump = catalog[ "pump" ];
while ( true )
    pump -> Drain();

That's all. If you want, you can have a look at the wallaroo mineplant sample to see all the details about this example.