Menu

Tutorial

Featured (4)
Anonymous

How to start simplify your (developer) life with Wallaroo.

Wallaroo 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:

  • you can create the right instance in SumpPump constructor
  • you can pass the instance of SafeEngine as parameter in SumpPump constructor
    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 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 behaviour without recompiling your application.

Enter Wallaroo

Instead, I really would like to be able to change the type of the classes withouth modifying them and withouth propagate 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>

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.


Related

Wiki: GettingStarted
Wiki: TableOfContents
Wiki: WiringFiles

Discussion

  • Anonymous

    Anonymous - 2013-09-08

    Originally posted by: ton...@gmail.com

    Where does TwoLevelSumpProbe? come from?

     
  • Anonymous

    Anonymous - 2013-09-08

    Originally posted by: daniele....@gmail.com

    TwoLevelSumpProbe and SafeEngine are the concrete implementations of SumpProbe and PumpEngine, respectively. In the tutorial I've not shown the implementation code of these two classes because the focus was on the wiring part.

    However, if you want a detailed explanation of this example, you can have a look at this article and the implementation you can find in the wallaroo mineplant sample.

    Anyway, I'm going to add some explanation in the tutorial.

    Thanks for pointing this out.

     
  • Anonymous

    Anonymous - 2014-04-23

    Originally posted by: fabien.c...@gmail.com

    I dont understand the reason of 2 parameters only ... Since wallaroo is intrusive and uses boost for file config loading, the client objects could be constrained to have a kind of map<string, boost::any> as single param in the contructor.

    So, instead of

    <code language="xml"> <device> <name>ferrari_f430</name> <class>Car</class> <parameter1> <type>string</type> <value>red</value> </parameter1> </device> </code>

    one could write :

    <code language="xml"> <device> <name>ferrari_f430</name> <class>Car</class> <parameter> <name>color</name> <type>string</type> <value>red</value> </parameter> </device> </code>

    What do you think ?

     
  • Anonymous

    Anonymous - 2014-04-28

    Originally posted by: daniele....@gmail.com

    Fabien, you're absolutely right.

    Actually, the current activity on wallaroo is precisely to define a mechanism to remove the constraint of the 2 parameters constructor. I think that once removed this limit, wallaroo can finally move to version 1.0 :-)

    The solution you propose is straightforward. I created this wiki page with some notes and comments about the problem and a couple of possible solutions. I'd be glad if you can have a look and maybe comment the solutions.

    Thank you very much for your contribute.

     
  • Anonymous

    Anonymous - 2014-08-13

    Originally posted by: oliverid...@gmail.com

    The tutorial is a bit wrong, DI allows to easy setup mocks yes, but main advantage is too keep Object graph simple and to allow to easily change your application wihtou having to touch zillion files and recompile everything.

    Also it allow to think in a little different way: If i need something I'll just inject it, without the need to create exotic stuff like "context structs/singletons/service locators" wich are all anti-patterns.

     
  • Anonymous

    Anonymous - 2014-08-14

    Originally posted by: daniele....@gmail.com

    Dario, thanks for your comment: I agree with you. However, I think the tutorial is not actually wrong: In my introduction I just quoted wikipedia. Sadly, the "Dependency Injection" entry in wikipedia says DI is particularly useful for providing "mock" test implementations of complex components. Altough you can use DI for that pourpose, I believe its real advantage is to create an "Object Oriented Structure" (as defined here). As you can see in the samples contained in wallaroo, I use DI to get "real extensibility" and not to provide mock tests ;-)

    Anyway. Soon, wallaroo will move to release 1.0. I think this is a good time to improve the wiki pages of the project. In particular, I'd like to add some pages about DI theory and its usage in real projects.

    Thanks for your contribute.

     

    Last edit: Anonymous 2017-03-18

Log in to post a comment.