implemented the standard code path of DependencyFactory

still mising: a mechanism to inject mock objects temporarily
This commit is contained in:
Fischlurch 2013-10-19 03:32:49 +02:00
parent ed7f975748
commit 739a473f7e
4 changed files with 274 additions and 144 deletions

View file

@ -97,18 +97,30 @@ namespace lib {
typedef DependencyFactory::InstanceConstructor Constructor;
/** default configuration of the dependency factory
* is to build a singleton instance on demand */
Depend()
{
factory.ensureInitialisation (buildSingleton<SI>());
}
/**
* optionally, the instance creation process can be configured
* optionally, the instance creation process can be explicitly configured
* \em once per type. By default, a singleton instance will be created.
* Installing another factory function enables other kinds of dependency injection;
* this configuration must be done prior to any use the dependency factory.
* @param ctor a constructor function, which will be invoked on first usage.
* @note basically a custom constructor function is responsible to manage any
* created service instances. Optionally it may install a deleter function
* via \c DependencyFactory::scheduleDestruction(void*,KillFun)
* @remark typically the \c Depend<TY> factory will be placed into a static variable,
* embedded into some service interface type. In this case, actual storage
* for this static variable needs to be allocated within some translation unit.
* embedded into another type or interface. In this case, actual storage for
* this static variable needs to be allocated within some translation unit.
* And this is the point where this ctor will be invoked, in the static
* initialisation phase of the respective translation unit (*.cpp)
*/
Depend (Constructor ctor = buildSingleton<SI>())
Depend (Constructor ctor)
{
factory.installConstructorFunction (ctor);
}
@ -151,18 +163,17 @@ namespace lib {
injectReplacement (SI* mock)
{
REQUIRE (mock);
factory.takeOwnership (mock); // EX_SANE
factory.takeOwnership (mock); // EX_SANE
SyncLock guard;
factory.shaddow (instance); // EX_FREE
instance = mock;
factory.shaddow (instance, mock); // EX_FREE
}
static void
dropReplacement()
{
SyncLock guard;
factory.restore (instance); // EX_FREE
factory.restore (instance); // EX_FREE
}
};

View file

@ -28,12 +28,71 @@
#include "lib/dependency-factory.hpp"
#include "lib/del-stash.hpp"
namespace lib {
namespace error = lumiera::error;
namespace { // private implementation details...
class AutoDestructor
{
DelStash destructionExecutor_;
static bool shutdownLock;
static AutoDestructor&
instance()
{
static AutoDestructor _instance_;
return _instance_;
}
~AutoDestructor()
{
shutdownLock = true;
}
static void
__lifecycleCheck()
{
if (shutdownLock)
throw error::Fatal("Attempt to re-access a service, "
"while Application is already in shutdown"
,error::LUMIERA_ERROR_LIFECYCLE);
}
public:
static void
schedule (void* object, DependencyFactory::KillFun customDeleter)
{
__lifecycleCheck();
instance().destructionExecutor_.manage (object, customDeleter);
}
static void
kill (void* object)
{
__lifecycleCheck();
instance().destructionExecutor_.kill (object);
}
};
bool AutoDestructor::shutdownLock = false;
// struct TemporarySwitch
// {
// void* originalInstance;
// InstanceConstructor originalCtor;
// };
//
// static TemporarySwitch temporarySwitch;
}
@ -41,5 +100,18 @@ namespace lib {
/** */
/** */
void
DependencyFactory::deconfigure (void* existingInstance)
{
AutoDestructor::kill (existingInstance);
}
void
DependencyFactory::scheduleDestruction (void* object, KillFun customDeleter)
{
AutoDestructor::schedule (object, customDeleter);
}
}// lib

View file

@ -32,122 +32,62 @@
#include "lib/error.hpp"
//#include "lib/sync-classlock.hpp"
#include "lib/del-stash.hpp" ////////TODO
namespace lib {
namespace error = lumiera::error;
class AutoDestructor
{
typedef void KillFun(void*);
DelStash destructionExecutor_;
static bool shutdownLock;
static AutoDestructor&
instance()
{
static AutoDestructor _instance_;
return _instance_;
}
~AutoDestructor()
{
shutdownLock = true;
}
static void
__lifecycleCheck()
{
if (shutdownLock)
throw error::Fatal("Attempt to re-access a service, "
"while Application is already in shutdown"
,error::LUMIERA_ERROR_LIFECYCLE);
}
public:
static void
schedule (void* object, KillFun* customDeleter)
{
__lifecycleCheck();
instance().destructionExecutor_.manage (object, customDeleter);
}
static void
kill (void* object)
{
__lifecycleCheck();
instance().destructionExecutor_.kill (object);
}
};
bool AutoDestructor::shutdownLock = false;
template<typename TAR>
struct InstanceHolder
: boost::noncopyable
{
TAR*
buildInstance ()
{
#if NOBUG_MODE_ALPHA
static uint callCount = 0;
ASSERT ( 0 == callCount++ );
#endif
// place new instance into embedded buffer
TAR* newInstance = new(buff_) TAR;
try
{
AutoDestructor::schedule (newInstance, &destroy_in_place);
return newInstance;
}
catch (std::exception& problem)
{
_kill_immediately (newInstance);
throw error::State (problem, "Failed to install a deleter function "
"for clean-up at application shutdown. ");
}
catch (...)
{
_kill_immediately (newInstance);
throw error::State ("Unknown error while installing a deleter function.");
}
}
private:
/** storage for the service instance */
char buff_[sizeof(TAR)];
static void
destroy_in_place (void* pInstance)
{
if (!pInstance) return;
static_cast<TAR*> (pInstance) -> ~TAR();
}
static void
_kill_immediately (void* allocatedObject)
{
destroy_in_place (allocatedObject);
const char* errID = lumiera_error();
WARN (memory, "Failure in DependencyFactory. Error flag was: %s", errID);
}
};
/**
/**
* Factory to generate and manage service objects classified by type.
*/
class DependencyFactory
{
public:
typedef void* (*InstanceConstructor)(void);
typedef void (*KillFun) (void*);
/** ensure initialisation by installing a default constructor function,
* but don't change an explicitly installed different constructor function.
* @remark deliberately this DependencyFactory has no constructor to
* initialise the object field \c ctorFunction_ to zero.
* The reason is, in the intended usage scenario, the
* DependencyFactory lives within a static variable,
* which might be constructed in no defined order
* in relation to the Depend<TY> instance.
*/
void
ensureInitialisation (InstanceConstructor defaultCtor)
{
if (!ctorFunction_)
this->ctorFunction_ = defaultCtor;
ENSURE (ctorFunction_);
}
/** explicitly set up constructor function, unless already configured
* In the default configuration, the template \c Depend<TY> installs a
* builder function to create a singleton instance in static memory.
* But specific instances might install e.g. a factory to create a
* implementation defined subclass; this might also be the place
* to hook in some kind of centralised service manager in future.
* @param ctor a function to be invoked to create a new service instance
* @throw error::Fatal when attempting to change an existing configuration.
*/
void
installConstructorFunction (InstanceConstructor ctor)
{
if (ctorFunction_ && ctor != ctorFunction_)
throw error::Fatal ("DependencyFactory: attempt to change the instance builder function "
"after-the-fact. Before this call, a different function was installed "
"and possibly also used already. Hint: visit all code locations, which "
"actually create an instance of the Depend<TY> template."
,error::LUMIERA_ERROR_LIFECYCLE);
this->ctorFunction_ = ctor;
}
/** invoke the installed ctor function */
void*
buildInstance()
@ -156,41 +96,96 @@ namespace lib {
return ctorFunction_();
}
void
deconfigure (void* existingInstance)
{
AutoDestructor::kill (existingInstance);
}
void deconfigure (void* existingInstance);
template<class TAR>
void
takeOwnership (TAR* newInstance)
{
UNIMPLEMENTED("enrol existing instance and prepare deleter function");
}
void takeOwnership (TAR*);
template<class TAR>
void
restore (TAR* volatile & activeInstance)
{
UNIMPLEMENTED("disable and destroy temporary shadowing instance and restore the dormant original instance");
}
void shaddow (TAR* volatile & activeInstance, TAR* replacement);
template<class TAR>
void
shaddow (TAR* volatile & activeInstance)
void restore (TAR* volatile & activeInstance);
/** hook to install a deleter function to clean up a service object.
* The standard constructor function uses this hook to schedule the
* destructor invocation on application shutdown; custom constructors
* are free to use this mechanism (or care for clean-up otherwise)
* @see lib::DelStash
*/
static void scheduleDestruction (void*, KillFun);
private:
/** pointer to the concrete function
* used for building new service instances */
InstanceConstructor ctorFunction_;
template<typename TAR>
class InstanceHolder
: boost::noncopyable
{
UNIMPLEMENTED("set up a temporary replacement, allowing to restore the original later");
}
/** storage for the service instance */
char buff_[sizeof(TAR)];
/** deleter function to invoke the destructor
* of the embedded service object instance.
* A pointer to this deleter function will be
* enrolled for execution at application shutdown
*/
static void
destroy_in_place (void* pInstance)
{
if (!pInstance) return;
static_cast<TAR*> (pInstance) -> ~TAR();
}
static void
_kill_immediately (void* allocatedObject)
{
destroy_in_place (allocatedObject);
const char* errID = lumiera_error();
WARN (memory, "Failure in DependencyFactory. Error flag was: %s", errID);
}
public:
TAR*
buildInstance ()
{
// place new instance into embedded buffer
TAR* newInstance = new(buff_) TAR;
try
{
scheduleDestruction (newInstance, &destroy_in_place);
return newInstance;
}
catch (std::exception& problem)
{
_kill_immediately (newInstance);
throw error::State (problem, "Failed to install a deleter function "
"for clean-up at application shutdown.");
}
catch (...)
{
_kill_immediately (newInstance);
throw error::State ("Unknown error while installing a deleter function.");
}
}
};
typedef void* (*InstanceConstructor)(void);
void
installConstructorFunction (InstanceConstructor ctor)
{
UNIMPLEMENTED("set up constructor function, unless already configured");
}
template<class TAR>
static void*
@ -208,10 +203,59 @@ namespace lib {
return & createSingletonInstance<TAR>;
}
private:
InstanceConstructor ctorFunction_;
};
namespace {
/** helper: destroy heap allocated object.
* This deleter function is used to clean-up
* a heap allocated mock object, which was installed
* as a temporary replacement for some service,
* typically during an unit test
*/
template<class X>
inline void
releaseOnHeap (void* o)
{
if (!o) return;
X* instance = static_cast<X*> (o);
delete instance;
}
}
template<class TAR>
void
DependencyFactory::takeOwnership (TAR* newInstance)
{
scheduleDestruction (newInstance, &releaseOnHeap<TAR>);
}
/**
* set up a temporary replacement, allowing to restore the original later
* @param activeInstance
* @param replacement
*/
template<class TAR>
void
DependencyFactory::shaddow (TAR* volatile & activeInstance, TAR* replacement)
{
ctorFunction_ = 0; /////TODO how to implement a functor without explicitly allocated storage??
activeInstance = replacement;
}
/**
* disable and destroy temporary shadowing instance and restore the dormant original instance
* @param activeInstance
*/
template<class TAR>
void
DependencyFactory::restore (TAR* volatile & activeInstance)
{
deconfigure (activeInstance);
activeInstance = NULL;
}
} // namespace lib
#endif

View file

@ -183,9 +183,12 @@ namespace test{
static void*
customFactoryFunction (void)
{
SubSubSub* newObject = static_cast<SubSubSub*> (DependencyFactory::createSingletonInstance<SubSubSub>());
newObject->instanceID_ = MAX_ID + 10;
return newObject;
static SubSubSub specialInstance;
// NOTE: the factory function is responsible
// for managing the instance's lifecycle
specialInstance.instanceID_ = MAX_ID + 10;
return &specialInstance;
}