DI: implement creating singleton from arbitrary (user provided) closure/functor/lambda

this is quite an ugly feature, but I couldn't come up with
any convincing argument *not* to implement it (and its low hanging fruit)
This commit is contained in:
Fischlurch 2018-03-22 06:53:56 +01:00
parent e74576f6b0
commit d9af3abb0f
4 changed files with 193 additions and 33 deletions

View file

@ -37,6 +37,7 @@
#include "lib/error.hpp"
#include "lib/depend2.hpp"
#include "lib/meta/function.hpp"
#include "lib/sync-classlock.hpp"
#include <type_traits>
@ -83,11 +84,34 @@ namespace lib {
static void
useSingleton()
{
__assert_compatible<SUB>();
static InstanceHolder<SUB> singleton;
installFactory ([&]()
useSingleton ([]{ return new SUB{}; });
}
/** configure dependency-injection for type SRV to manage a subclass singleton,
* which is created lazily on demand by invoking the given builder function
* @param ctor functor to create a heap allocated instance of subclass
* @throws error::Logic (LUMIERA_ERROR_LIFECYCLE) when the default factory has already
* been invoked at the point when calling this (re)configuration function.
*/
template<class FUN>
static void
useSingleton(FUN&& ctor)
{
using lib::meta::_Fun;
using lib::meta::Strip;
static_assert (_Fun<FUN>::value, "Need a Lambda or Function object to create a heap allocated instance");
using Ret = typename _Fun<FUN>::Ret;
using Sub = typename Strip<Ret>::TypePlain;
__assert_compatible<Sub>();
static_assert (std::is_pointer<Ret>::value, "Function must yield a pointer to a heap allocated instance");
static InstanceHolder<Sub> singleton;
installFactory ([ctor]()
{
return singleton.buildInstance();
return singleton.buildInstance (ctor);
});
}
@ -167,12 +191,18 @@ namespace lib {
public:
Local()
: Local([]{ return new MOC{}; })
{ }
template<class FUN>
explicit
Local (FUN&& buildInstance)
{
__assert_compatible<MOC>();
temporarilyInstallAlternateFactory (origInstance_, origFactory_
,[this]()
,[=]()
{
mock_.reset(new MOC{});
mock_.reset (buildInstance());
return mock_.get();
});
}

View file

@ -101,6 +101,13 @@ namespace lib {
public:
TAR*
buildInstance()
{
return buildInstance ([]{ return new TAR{}; });
}
template<class FUN>
TAR*
buildInstance(FUN&& ctor)
{
if (instance_)
throw error::Fatal("Attempt to double-create a singleton service. "
@ -108,8 +115,7 @@ namespace lib {
"or runtime system is seriously broken"
,error::LUMIERA_ERROR_LIFECYCLE);
// place new instance into embedded buffer
instance_.reset (new TAR{});
instance_.reset (ctor());
return instance_.get();
}
};
@ -119,7 +125,7 @@ namespace lib {
{
public:
ABS*
buildInstance()
buildInstance(...)
{
throw error::Fatal("Attempt to create a singleton instance of an abstract class. "
"Application architecture or lifecycle is seriously broken.");

View file

@ -109,10 +109,10 @@ namespace test{
verify_Singleton();
verify_SubclassSingleton();
verify_expose_Service_with_Lifecycle();
verify_customFactory();
verify_automaticReplacement();
verify_customFactory();
CHECK (7+5+1 == checksum); // singletons stay alive until application shutdown
CHECK (9+7+5+1 == checksum); // singletons stay alive until application shutdown
}
@ -204,15 +204,6 @@ namespace test{
/** @test instance creation can be preconfigured with a closure */
void
verify_customFactory()
{
TODO ("implement arbitrary ctor closure");
}
/** @test injecting test mocks temporarily */
void
verify_automaticReplacement()
@ -311,6 +302,90 @@ namespace test{
CHECK ((1+5+7)*7 == dumm().probe() );
CHECK ((1+5+7) == checksum );
}
/** @test instance creation can be preconfigured with a closure
* Both Singleton and Test-Mock creation can optionally be performed through a
* user provided Lambda or Functor. To demonstrate this, we use a `Veryspecial` local class,
* which takes an `int&` as constructor parameter -- and we create the actual instance through
* a lambda, which happens to capture a local variable by reference.
* @warning this can be dangerous; in the example demonstrated here, the created singleton instance
* continues to live until termination of the test-suite. After leaving this test function,
* it thus holds a dangling reference, pointing into stack memory....
*/
void
verify_customFactory()
{
CHECK ((1+5+7) == checksum );
struct Veryspecial
: Dummy<9>
{
Veryspecial(int& ref)
: magic_{ref}
{ }
int& magic_;
virtual int
probe() override
{
return magic_++;
}
};
int backdoor = 22;
DependInject<Dummy<9>>::useSingleton ([&]{ return new Veryspecial{backdoor}; });
CHECK ((1+5+7) == checksum );
CHECK ( 22 == backdoor );
Depend<Dummy<9>> tricky;
CHECK ((1+5+7) == checksum );
CHECK (22 == backdoor );
CHECK (22 == tricky().probe());
CHECK (23 == backdoor );
CHECK ((1+5+7+9) == checksum ); // Veryspecial Dummy<9> subclass was created on the heap
// and will continue to live there until the testsuite terminates
backdoor = 41;
CHECK (41 == tricky().probe());
CHECK (42 == backdoor );
Depend<Dum> dumm;
CHECK ((1+5+7+9)*7 == dumm().probe() );
{////////////////////////////////////////////////////TEST-Scope
DependInject<Dum>::Local<Dum> insidious ([&]{ return new Veryspecial{backdoor}; });
CHECK ((1+5+7+9) == checksum );
CHECK (not insidious);
CHECK (42 == dumm().probe() );
CHECK (43 == backdoor );
CHECK ((1+5+7+9+9) == checksum );
CHECK (isSameObject (dumm(), *insidious));
CHECK (43 == tricky().probe());
CHECK (44 == backdoor );
backdoor = -1;
CHECK (-1 == dumm().probe() );
CHECK ( 0 == backdoor );
CHECK ((1+5+7+9+9) == checksum );
}////////////////////////////////////////////////////(End)TEST-Scope
CHECK ((1+5+7+9) == checksum );
CHECK ((1+5+7+9)*7 == dumm().probe() );
CHECK ( 0 == tricky().probe());
CHECK (+1 == backdoor );
} // NOTE: Veryspecial holds a dangling reference into stack memory from now on!
};

View file

@ -26792,10 +26792,16 @@
</node>
<node CREATED="1521207954859" ID="ID_631339002" MODIFIED="1521418307590" TEXT="Analyse">
<icon BUILTIN="idea"/>
<node CREATED="1521208242178" ID="ID_539882602" MODIFIED="1521208246821" TEXT="erforderliche Mechanismen">
<node CREATED="1521208254376" ID="ID_1933406851" MODIFIED="1521208256603" TEXT="Singleton">
<node CREATED="1521208339124" ID="ID_287433738" MODIFIED="1521209063045" TEXT="Closure &#xfc;ber konkreten Ctor"/>
<node CREATED="1521208339124" ID="ID_826329078" MODIFIED="1521387972747" TEXT="(optional)Closure mit speziellen Argumenten"/>
<node CREATED="1521208242178" ID="ID_539882602" MODIFIED="1521696589693" TEXT="erforderliche Mechanismen">
<icon BUILTIN="info"/>
<node COLOR="#338800" CREATED="1521208254376" ID="ID_1933406851" MODIFIED="1521696578693" TEXT="Singleton">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1521208339124" ID="ID_287433738" MODIFIED="1521696567487" TEXT="Closure &#xfc;ber konkreten Ctor">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1521208339124" ID="ID_826329078" MODIFIED="1521696572039" TEXT="(optional)Closure mit speziellen Argumenten">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1521209708336" ID="ID_1667188609" MODIFIED="1521332921903" TEXT="Storage f&#xfc;r UnterBla-Instanz bereitstellen">
<icon BUILTIN="button_ok"/>
</node>
@ -26861,8 +26867,11 @@
</node>
</node>
</node>
<node CREATED="1521208261887" ID="ID_384054163" MODIFIED="1521208264827" TEXT="Local">
<node CREATED="1521209044203" ID="ID_1994153994" MODIFIED="1521409010023" TEXT="(optional)Closure mit speziellen Argumenten "/>
<node COLOR="#338800" CREATED="1521208261887" ID="ID_384054163" MODIFIED="1521696582060" TEXT="Local">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1521209044203" ID="ID_1994153994" MODIFIED="1521696576989" TEXT="(optional)Closure mit speziellen Argumenten ">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1521209708336" ID="ID_1870723126" MODIFIED="1521411844636" TEXT="Storage f&#xfc;r UnterBla-Instanz bereitstellen">
<icon BUILTIN="button_ok"/>
</node>
@ -27049,7 +27058,7 @@
<node CREATED="1521242824239" ID="ID_1013563756" MODIFIED="1521242834313" TEXT="inline-wegwerf-Objekt"/>
</node>
</node>
<node CREATED="1521240053852" FOLDED="true" ID="ID_666003564" MODIFIED="1521418511493" STYLE="fork" TEXT="Detail-Fragen">
<node CREATED="1521240053852" FOLDED="true" ID="ID_666003564" MODIFIED="1521696619000" STYLE="fork" TEXT="Detail-Fragen">
<icon BUILTIN="button_ok"/>
<node COLOR="#990000" CREATED="1521169099047" ID="ID_1183392158" MODIFIED="1521418281285" TEXT="DependencyFactory-Rahmenklasse">
<icon BUILTIN="button_cancel"/>
@ -27219,8 +27228,8 @@
<node COLOR="#338800" CREATED="1521418571594" ID="ID_1432227459" MODIFIED="1521688407751" TEXT="DependInject&lt;SRV&gt; ersetzt dependency-factory.hpp">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1521418598463" ID="ID_237484313" MODIFIED="1521418617389" TEXT="Objekterzeugung erweitern">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1521418598463" ID="ID_237484313" MODIFIED="1521696529892" TEXT="Objekterzeugung erweitern">
<icon BUILTIN="button_ok"/>
<node COLOR="#990000" CREATED="1521418618052" ID="ID_1253147848" MODIFIED="1521688468798" TEXT="auf beliebige Argumente">
<icon BUILTIN="stop-sign"/>
<node CREATED="1521688471643" ID="ID_1037697154" MODIFIED="1521688504558" TEXT="gef&#xe4;hrlich wenn Objekterzeugung lazy">
@ -27231,8 +27240,42 @@
<icon BUILTIN="button_ok"/>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1521418627130" ID="ID_206705145" MODIFIED="1521418639801" TEXT="auf allgemeine Closures">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1521418627130" ID="ID_206705145" MODIFIED="1521696521621" TEXT="auf allgemeine Closures">
<icon BUILTIN="button_ok"/>
<node CREATED="1521696315169" ID="ID_914761003" MODIFIED="1521696326988" TEXT="ob das wohl geht...."/>
<node CREATED="1521696477035" ID="ID_1232450528" MODIFIED="1521696510682" TEXT="Verdammter Mist! Wird die Closure nun kopiert oder geMOVEd?"/>
<node BACKGROUND_COLOR="#fdfdcf" COLOR="#ff0000" CREATED="1521696673440" ID="ID_1408713510" MODIFIED="1521696682160" TEXT="compile-time Checks mal verifizieren">
<icon BUILTIN="flag-pink"/>
</node>
<node CREATED="1521696334383" ID="ID_1627167666" MODIFIED="1521696684626" TEXT="gef&#xe4;llt mir nicht wirklich">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<body>
<p>
denn nun wird das &quot;singleton&quot; schon ziemlich gehaltlos,
</p>
<p>
und es ist einigerma&#223;en undurchsichtig, wo nun die Instanz erzeugt wird.
</p>
<p>
</p>
<p>
Allerdings gibt es auch kein stichhaltiges Argument, dieses Feature<i>&#160;nicht</i>&#160;zu implementieren.
</p>
<p>
Es ist halt einfach nahheliegend, da&#223; man mal eine Subklasse mit abweichenden Parametern
</p>
<p>
konstruieren wollen k&#246;nnte, und es ist von der Implementierung her &quot;quasi geschenkt&quot;.
</p>
</body>
</html>
</richcontent>
<icon BUILTIN="smily_bad"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1521433938944" ID="ID_546086969" MODIFIED="1521433946432" TEXT="double-checked-locking">
@ -27286,8 +27329,14 @@
<node COLOR="#338800" CREATED="1521433807714" ID="ID_655174082" MODIFIED="1521433832327" TEXT="Assertions explizit machen">
<icon BUILTIN="button_ok"/>
</node>
<node BACKGROUND_COLOR="#eee5c3" COLOR="#990000" CREATED="1521433833942" ID="ID_841958834" MODIFIED="1521433843486" TEXT="Factory-Closure abdecken">
<icon BUILTIN="flag-yellow"/>
<node COLOR="#338800" CREATED="1521433833942" ID="ID_841958834" MODIFIED="1521696534947" TEXT="Factory-Closure abdecken">
<icon BUILTIN="button_ok"/>
<node COLOR="#338800" CREATED="1521696537522" ID="ID_1639668362" MODIFIED="1521696552680" TEXT="f&#xfc;r subclass-Singleton">
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1521696547161" ID="ID_413219021" MODIFIED="1521696551785" TEXT="f&#xfc;r Test-Mock">
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1521433852660" ID="ID_1581010755" MODIFIED="1521433857543" TEXT="l&#xe4;uft">
<icon BUILTIN="button_ok"/>