How to start simplify your (developer) life with Wallaroo.
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.
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:
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.
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.
Wiki: GettingStarted
Wiki: TableOfContents
Wiki: WiringFiles
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
Originally posted by: ton...@gmail.com
Where does TwoLevelSumpProbe? come from?
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
Originally posted by: daniele....@gmail.com
TwoLevelSumpProbe
andSafeEngine
are the concrete implementations ofSumpProbe
andPumpEngine
, 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.
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
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 ?
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
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.
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
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.
View and moderate all "wiki Discussion" comments posted by this user
Mark all as spam, and block user from posting to "Wiki"
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