logo

Wallaroo

C++ Dependency Injection

Getting Started

Getting Wallaroo

The easiest way to get a copy of Wallaroo is to download an archive from the page Downloads of the sourceforge project. Wallaroo is delivered in a ZIP file for Windows users and in a compressed TAR file (.tgz) for Unix/Linux users. Both archives contain the same files, the only difference is that all text files in the ZIP files have line endings suitable for Windows (CR-LF), while the text files in the TAR file have line endings suitable for Unix/Linux (LF only).

Otherways, you can clone the git repository from the repository page.

Compiling Wallaroo

Wallaroo library is header-only: it consists entirely of header files containing templates and inline functions, and require no separately-compiled library binaries or special treatment when linking.

External Dependencies

Wallaroo relies on some of the famous boost libraries. You can download the latest version from here, extract it wherever you want in your hard disk and include its path when compiling your application that uses Wallaroo.

Since Wallaroo uses header only boost libraries, you don't need to compile boost.

Starting from v. 0.2, if your compiler supports C++11 features, you can get rid of boost libraries and relies only on the new C++ standard features.

How to build the examples

You can find some examples in the directory samples. Each example comes with:

All methods require you to specify the boost library path. You can do it in the following ways.

Make

make UCFLAGS=-I<boost_path>

example:

make UCFLAGS=-I/opt/boost_1_66_0/

Visual Studio

You must set the environment variable BOOST.

How to build your own project

To build your own project, the compiler needs to know the include path of the wallaroo libraries.

If your compiler is not C++11 compliant (or if you plan to use XmlConfiguration or JsonConfiguration classes), you should also provide the boost path.

For example, if you use gcc, you can use the following syntax (with boost):

g++ -Wall -I<boost_path> -I<wallaroo_path> *.cpp -o <app_exe>

or (without boost):

g++ -Wall -std=c++0x -I<wallaroo_path> *.cpp -o <app_exe>

Using Wallaroo

Using Wallaroo you will be able to separate the setup phase (object building and wiring) from the logic of your classes. In addition, you will be able to change the class of your objects without rebuilding anything: in this way you can change the behavior of your application, or perform mock testing using the same executable.

With Wallaroo you will be able to focus on the logic of your classes, without worrying about the wiring phase.

This mechanism is called Dependency Injection. Someone calls it Inversion of Control, but actually, that has a broader meaning (see here).

Parts and Collaborators

The syntax of Wallaroo is based on the catalog metaphor. The objects you build and link together are "parts", that use "collaborators". You can add a dependency from a part to another by linking a collaborator of the first to the second.

Basics

To be instantiable by wallaroo, a class must derive from wallaroo::Part. So, to make a class Car instantiable by wallaroo, you must declare it in a header file in this way:

#include "wallaroo/registered.h"

using namespace wallaroo;

class Car : public Part
{
public:
    Car( const string& color );
    // other methods declaration here
    ...
};

and you must define it in the implementation file in this way:

WALLAROO_REGISTER( Car, string )

Car::Car( const string& color )
{
...
}

// other methods definition here
...

The parameter string you can see inside the clause WALLAROO_REGISTER is the parameter type of the Car constructor. Wallaroo can manage constructors having up to 2 parameters (see wallaroo::Attribute for a way to overcome this limit).

The previous declaration and definition, allow you to create instances of Car in this way:

#include "wallaroo/catalog.h"

using namespace wallaroo;

...
Catalog catalog; // this is the container of your objects

// populate the catalog with some objects:
catalog.Create( "ferrari_f430", "Car", string( "red" ) );
catalog.Create( "maserati_granturismo", "Car", string( "black" ) );
...

The methods used have the following signature:

template< class P1 , class P2 >
void Catalog::Create( const std::string &id, const std::string &className, const P1 &p1, const P2 &p2 )
 
template< class P >
void Catalog::Create( const std::string &id, const std::string &className, const P &p )
 
void Catalog::Create( const std::string &id, const std::string &className )

The first parameter is the name of the instance you're going to create, the second is the name of its class and the (optional) others parameters are the parameters you want to pass to the constructor of the object.

After filling the catalog, you can get a (smart) pointer to an object in this way:

shared_ptr< Car > maserati = catalog[ "maserati_granturismo" ];

So far so good. Now, to do useful work, your objects need to talk to each other. For example, class Car may need to invoke Engine methods.

With this code you're saying that class Car has a "pointer" to reach an object of class Engine:

class Car : public Part
{
...
private:
    Collaborator< Engine > engine;
...
};

Please note that by deriving Car from Part you make it possible associate a specific instance of class Engine to the member engine at run-time.

In the Car constructor, you must specify that Car has an engine Collaborator named mainEngine:

Car::Car( const std::string& color ) :
    engine( "mainEngine", RegistrationToken() )
{
...
}

Inside the Car implementation you can use it as a standard pointer:

void Car::Start()
{
    engine -> SwitchOn();
}

Just remember that Collaborator< Engine > is really a weak_ptr: if in the meantime someone deleted the pointed object, when you try to use it you will get a Wallaroo::DeletedPartError exception.

Please note that you can have a collaborator container in your classes, by using the syntax:

Collaborator< Book, collection > library;

by default, in this case, wallaroo will use std::vector. However, you can specify another std container in this way:

Collaborator< Book, collection, std::list > library;

You can also provide constraints about a collaborator, i.e. you can specify if a collaborator is optional, mandatory (default) or its range (for a collection collaborator):

class Car : public Part
{
    ...
private:
    Collaborator< Engine > engine; // one instance of Engine
    Collaborator< AirConditioning, optional > airConditioning; // zero or one instance of airConditioning
    Collaborator< Airbag, collection > airbags; // an indeterminate number of airbags (std::vector by default)
    Collaborator< Speaker, collection, std::list > speakers; // an indeterminate number of speaker in a std::list
    Collaborator< Seat, bounded_collection< 2, 6 > > seats; // from 2 to 6 instances of seats
    ...
};

So far so good. But how can you link engine to a previously created object? With this code:

use( catalog[ "f136e" ] ).as( "mainEngine" ).of( catalog[ "ferrari_f430" ] ); // this means ferrari_f430.engine=f136e

In this way you assigned the object f136e to the role engine of the object ferrari_f430. This wiring operation can be done in the main, where you set up all your application, or wherever you like in your code, providing you have access to catalog.

If your application uses a unique catalog, wallaroo provides the following syntax to avoid you some typing:

Catalog myCatalog;
...
wallaroo_within( myCatalog )
{
    use( "f136e" ).as( "engine" ).of( "ferrari_f430" );
    use( "m139p" ).as( "engine" ).of( "maserati_granturismo" );
}

this is equivalent to:

Catalog myCatalog;
...
use( myCatalog[ "f136e" ] ).as( "engine" ).of( myCatalog[ "ferrari_f430" ] );
use( myCatalog[ "m139p" ] ).as( "engine" ).of( myCatalog[ "maserati_granturismo" ] );

the latter code is useful when your objects are stored in multiple catalogs.

After performing the wiring, you can check the constraints by using Catalog::IsWiringOk() (that returns a bool) or Catalog::CheckWiring() (that throws an exception).

Loading the configuration from a file

By using strings, both creation and wiring information can be loaded from a configuration file or external DB.

You can build your own mechanism to load a spec from a file and fill a wallaroo::Catalog with the objects built and configured or you can use the wallaroo::XmlConfiguration, wallaroo::JsonConfiguration or wallaroo::Configuration classes, that let you fill a catalog loading respectively an xml or json file (the syntax is explained here).

This is the content of the xml file:

<wallaroo>

  <parts>

    <part>
      <name>ferrari_f430</name>
      <class>Car</class>
      <parameter1>
        <type>string</type>
        <value>red</value>
      </parameter1>
    </part>

    <part>
      <name>maserati_granturismo</name>
      <class>Car</class>
      <parameter1>
        <type>string</type>
        <value>black</value>
      </parameter1>
    </part>

    <part>
      <name>m139p</name>
      <class>Engine</class>
    </part>

    <part>
      <name>f136e</name>
      <class>Engine</class>
    </part>

  </parts>

  <wiring>

    <wire>
      <source>ferrari_f430</source>
      <dest>f136e</dest>
      <collaborator>mainEngine</collaborator>
    </wire>

    <wire>
      <source>maserati_granturismo</source>
      <dest>m139p</dest>
      <collaborator>mainEngine</collaborator>
    </wire>

  </wiring>

</wallaroo>

This is the content of the json file:

{
  "wallaroo":
  {

    "parts":
    [
      {
        "name": "ferrari_f430",
        "class": "Car",
        "parameter1":
        {
          "type": "string",
          "value": "red"
        }
      },

      {
        "name": "maserati_granturismo",
        "class": "Car",
        "parameter1":
        {
          "type": "string",
          "value": "black"
        }
      },

      {
        "name": "m139p",
        "class": "Engine"
      },

      {
        "name": "f136e",
        "class": "Engine"
      }

    ],

    "wiring":
    [

      {
        "source": "ferrari_f430",
        "dest": "f136e",
        "collaborator": "mainEngine"
      },

      {
        "source": "maserati_granturismo",
        "dest": "m139p",
        "collaborator": "mainEngine"
      }

    ]
  }
}

And, finally, this is the content of the wal file:

    ferrari_f430 = new Car( color="red" );
    maserati_granturismo = new Car( color="black" );
    
    m139p = new Engine;
    f136e = new Engine;
    
    ferrari_f430.mainEngine = f136e;
    maserati_granturismo.mainEngine = m139p;
(Please, note that in the wal file we assign a color attribute, and not a constructor parameter: this because the wal syntax does not provide support for constructor parameters).

So, instead of populating manually the catalog, you can do it by loading a xml file:

Catalog catalog;
XmlConfiguration file( "wiring.xml" );
file.Fill( catalog );

or, a json file:

Catalog catalog;
JsonConfiguration file( "wiring.json" );
file.Fill( catalog );

or, a wal file:

Catalog catalog;
Configuration file( "wiring.wal" );
file.Fill( catalog );

Defining wallaroo classes in shared libraries

For shake of extendibility you can decide to put your class definitions in shared libraries to get simple plugin mechanism.

The code of your class will remain the same, but you must compile it in a shared library (.dll on windows or .so on unix-like os). You can put declaration and definition in a implementation file:

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

using namespace wallaroo;

class Car : public Part
{
public:
    Car();
    // other methods declaration here
    ...
};

WALLAROO_DYNLIB_REGISTER( Car )

Car::Car()
{
...
}

// other methods definition here
...

Please note that in this case, you use WALLAROO_DYNLIB_REGISTER instead of WALLAROO_REGISTER. You must include "dynamic_lib.h" only once per library and "dyn_registered.h" in every translation unit where you use WALLAROO_DYNLIB_REGISTER.

The current version of wallaroo only supports the definition of classes with zero-parameters constructor in shared libraries.

To load the classes definition, you must use the static method Plugin::Load() at the beginning of your application:

#include "wallaroo/catalog.h"

using namespace wallaroo;

...
// load classes in shared libraries:
Plugin::Load( "car" + Plugin::Suffix() ); // Plugin::Suffix() expands to .dll or .so according to the OS
...
Catalog catalog; // this is the container of your objects

// populate the catalog with some objects:
catalog.Create( "ferrari_f430", "Car" );
catalog.Create( "maserati_granturismo", "Car" );
...

Alternatively, you can specify the shared libraries to load in the configuration file:

<wallaroo>

  <plugins>
    <shared>car</shared>
    <shared>engine</shared>
  </plugins>

  <parts>
    ...
  </parts>

  <wiring>
    ...
  </wiring>

</wallaroo>

or

{
  "wallaroo":
  {

    "plugins":
    {
      "shared": "car",
      "shared": "engine"
    },

    "parts":
    [
      ...
    ],

    "wiring":
    [
      ...
    ]
  }
}

or

@load "car";
@load "engine;

...

then, in the main application, you must specify to load the shared libraries specified in the configuration file:

#include "wallaroo/catalog.h"
#include "wallaroo/xmlconfiguration.h"

using namespace wallaroo;

...
XmlConfiguration file( "configuration.xml" );
// load classes in shared libraries:
file.LoadPlugins();
...

Where to go from here

This page has given you a general overview of Wallaroo.

You can obtain more information in the following places: