From 043dc948e3ac9f2a64a5041c0b2b7bd02d5379f4 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Fri, 30 Mar 2018 18:44:43 +0200 Subject: [PATCH] DI: (WIP) prepare for switch to the new implementation (#1086) --- src/lib/depend.hpp | 217 -------------------------------- src/lib/dependency-factory.hpp | 223 --------------------------------- src/lib/test/depend-4test.hpp | 123 ------------------ 3 files changed, 563 deletions(-) delete mode 100644 src/lib/depend.hpp delete mode 100644 src/lib/dependency-factory.hpp delete mode 100644 src/lib/test/depend-4test.hpp diff --git a/src/lib/depend.hpp b/src/lib/depend.hpp deleted file mode 100644 index c89a308d5..000000000 --- a/src/lib/depend.hpp +++ /dev/null @@ -1,217 +0,0 @@ -/* - DEPEND.hpp - access point to singletons and dependencies - - Copyright (C) Lumiera.org - 2013, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -==================================================================== -This code is heavily inspired by - The Loki Library (loki-lib/trunk/include/loki/Singleton.h) - Copyright (c) 2001 by Andrei Alexandrescu - Loki code accompanies the book: - Alexandrescu, Andrei. "Modern C++ Design: Generic Programming - and Design Patterns Applied". - Copyright (c) 2001. Addison-Wesley. ISBN 0201704315 - -*/ - - - -/** @file depend.hpp - ** Singleton services and Dependency Injection. - ** The Singleton Pattern provides a single access point to a class or - ** service and exploits this ubiquitous access point to limit the number of objects - ** of this type to a single shared instance. Within Lumiera, we mostly employ a - ** factory template for this purpose; the intention is to use on-demand initialisation - ** and a standardised lifecycle. In the default configuration, this \c Depend factory - ** maintains a singleton instance of type TY. The possibility to install other factory - ** functions allows for subclass creation and various other kinds of service management. - ** - ** - ** # Why Singletons? Inversion-of-Control and Dependency Injection - ** - ** Singletons are frequently over-used, and often they serve as disguised - ** global variables to support a procedural programming style. As a remedy, typically - ** the use of a »Dependency Injection Container« is promoted. And -- again typically -- - ** these DI containers tend to evolve into heavyweight universal tools and substitute - ** the original problem by metadata hell. - ** - ** Thus, for Lumiera, the choice to use Singletons was deliberate: we understand the - ** Inversion-of-Control principle, yet we want to stay just below the level of building - ** a central application manager core. At the usage site, we access a factory for some - ** service *by name*, where the »name« is actually the type name of an interface or - ** facade. Singleton is used as an _implementation_ of this factory, when the service - ** is self-contained and can be brought up lazily. - ** - ** ## Conventions, Lifecycle and Unit Testing - ** - ** Usually we place an instance of the singleton factory (or some other kind of factory) - ** as a static variable within the interface class describing the service or facade. - ** As a rule, everything accessible as Singleton is sufficiently self-contained to come - ** up any time -- even prior to `main()`. But at shutdown, any deregistration must be done - ** explicitly using a lifecycle hook. Destructors aren't allowed to do _any significant work_ - ** beyond releasing references, and we acknowledge that singletons can be released - ** in _arbitrary order_. - ** - ** @deprecated 3/18 rework of the singleton / dependency factory is underway - ** - ** @see lib::Depend - ** @see lib::DependencyFactory - ** @see lib::test::Depend4Test - ** @see singleton-test.cpp - ** @see dependency-factory-test.cpp - */ - - -#ifndef LIB_DEPEND_H -#define LIB_DEPEND_H - - -#include "lib/sync-classlock.hpp" -#include "lib/dependency-factory.hpp" - - -namespace lib { - - /** - * Access point to singletons and other kinds of dependencies. - * Actually this is a Factory object, which is typically placed into a - * static field of the Singleton (target) class or some otherwise suitable interface. - * @param SI the class of the Singleton instance - * @note uses static fields internally, so all factory configuration is shared per type - * @remark there is an ongoing discussion regarding the viability of the - * Double Checked Locking pattern, which requires either the context of a clearly defined - * language memory model (as in Java), or needs to be supplemented by memory barriers. - * In our case, this debate boils down to the question: does \c pthread_mutex_lock/unlock - * constitute a memory barrier, such as to force any memory writes happening \em within - * the singleton ctor to be flushed and visible to other threads when releasing the lock? - * To my understanding, the answer is yes. See - * [POSIX](http://www.opengroup.org/onlinepubs/000095399/basedefs/xbd_chap04.html#tag_04_10) - * @remark we could consider to rely on a _Meyers Singleton_, where the compiler automatically - * generates the necessary code and guard variable to ensure single-threaded initialisation - * of the instance variable. But the downside of this approach is that we'd loose access - * to the singleton instance variable, which then resides within the scope of a single - * access function. Such would counterfeit the ability to exchange the instance to - * inject a mock for unit testing. - * @deprecated 3/18 rework of the singleton / dependency factory is underway /////////////////////TICKET #1086 - * @param SI the class of the Singleton instance - */ - template - class Depend - { - typedef ClassLock SyncLock; - - static SI* volatile instance; - static DependencyFactory factory; - - - public: - /** Interface to be used by clients for retrieving the service instance. - * Manages the instance creation, lifecycle and access in multithreaded context. - * @return instance of class SI. When used in default configuration, - * this service instance is a singleton - */ - SI& - operator() () - { - if (!instance) - { - SyncLock guard; - - if (!instance) - instance = static_cast (factory.buildInstance()); - } - ENSURE (instance); - return *instance; - } - - - - typedef DependencyFactory::InstanceConstructor Constructor; - - - /** default configuration of the dependency factory - * is to build a singleton instance on demand */ - Depend() - { - factory.ensureInitialisation (buildSingleton()); - } - - /** - * optionally, the instance creation process can be configured explicitly - * \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 \em 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. - * @remark typically the \c Depend factory will be placed into a static variable, - * 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) - { - factory.installConstructorFunction (ctor); - } - - // standard copy operations applicable - - - - /* === Management / Test support interface === */ - - /** temporarily replace the service instance. - * The purpose of this operation is to support unit testing. - * @param mock reference to an existing service instance (mock). - * @return reference to the currently active service instance. - * @warning this is a dangerous operation and not threadsafe. - * Concurrent accesses might still get the old reference; - * the only way to prevent this would be to synchronise - * \em any access (which is too expensive). - * This feature should only be used for unit tests thusly. - * @remark the replacement is not actively managed by the DependencyFactory, - * it remains in ownership of the calling client (unit test). Typically - * this test will keep the returned original service reference and - * care for restoring the original state when done. - * @see Depend4Test scoped object for automated test mock injection - */ - static SI* - injectReplacement (SI* mock) - { - SyncLock guard; - SI* currentInstance = instance; - instance = mock; - return currentInstance; - } - }; - - - - - // Storage for static per type instance management... - template - SI* volatile Depend::instance; - - template - DependencyFactory Depend::factory; - - - -} // namespace lib -#endif diff --git a/src/lib/dependency-factory.hpp b/src/lib/dependency-factory.hpp deleted file mode 100644 index 7330e710b..000000000 --- a/src/lib/dependency-factory.hpp +++ /dev/null @@ -1,223 +0,0 @@ -/* - DEPENDENCY-FACTORY.hpp - managing the lifecycle of singletons and dependencies - - Copyright (C) Lumiera.org - 2013, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -/** @file dependency-factory.hpp - ** Implementation of a singleton factory used to bring up services as dependency. - ** @internal this implementation header belongs to our framework to deal with - ** [service dependencies](\ref depend.hpp) and should not be used directly. - ** @deprecated 3/18 rework of the singleton / dependency factory is underway - */ - - - - -#ifndef LIB_DEPENDENCY_FACTORY_H -#define LIB_DEPENDENCY_FACTORY_H - - - -#include "lib/nobug-init.hpp" -#include "lib/error.hpp" - - -namespace lib { - - namespace error = lumiera::error; - - - /** - * @internal Factory to generate and manage service objects classified by type. - * An instance of this factory is placed once for each type for use by - * the lib::Depend front-end for dependency management. While the latter - * provides the singleton-style initialisation patter, the DependencyFacotry - * maintains a customisable factory function for instance creation. Moreover, - * the embedded helper template DependencyFactory::InstanceHolder actually - * creates and manages the singleton instances in default configuration; - * it is placed into a function-scope static variable; consequently - * the singleton instances are placed into static memory by default. - * @deprecated 3/18 rework of the singleton / dependency factory is underway - */ - class DependencyFactory - { - public: - typedef void* (*InstanceConstructor)(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 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 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 template." - ,error::LUMIERA_ERROR_LIFECYCLE); - this->ctorFunction_ = ctor; - } - - - - /** invoke the installed ctor function */ - void* - buildInstance() - { - if (!ctorFunction_) - throw error::Fatal ("lib::Depend: attempt to retrieve a service object prior to initialisation " - "of the DependencyFactory. Typically, this happens due to a misconfiguration " - "regarding static initialisation order. When lib::Depend is placed into " - "a class static variable, then the definition and initialisation of that " - "variable must happen prior to the call which caused this exception." - ,error::LUMIERA_ERROR_LIFECYCLE); - return ctorFunction_(); - } - - - - - - private: - /** pointer to the concrete function - * used for building new service instances */ - InstanceConstructor ctorFunction_; - - - - /** function to build service instances. - * A service class with private ctor can declare DependencyFactory as friend, - * to indicate this is the expected way to create instances */ - template - static TAR* - create_in_buffer (void* buffer) - { - return new(buffer) TAR; - } - - - /** - * @internal Helper to manage a service instance within an embedded buffer. - * This helper and thus the service instance will be allocated into static memory. - */ - template - class InstanceHolder - : util::NonCopyable - { - /** storage for the service instance */ - char buff_[sizeof(TAR)]; - uint lifecycle_; - - - public: - InstanceHolder() - : lifecycle_(0) - { } - - ~InstanceHolder() - { - lifecycle_ |= 4; - if (1 & lifecycle_) - { - reinterpret_cast (buff_). ~TAR(); - --lifecycle_; - } } - - - TAR* - buildInstance () - { - if (0 < lifecycle_) - throw error::Fatal("Attempt to double-create a singleton service. " - "Either the application logic, or the compiler " - "or runtime system is seriously broken" - ,error::LUMIERA_ERROR_LIFECYCLE); - - // place new instance into embedded buffer - TAR* newInstance = create_in_buffer(buff_); - ++lifecycle_; - return newInstance; - } - }; - - - - template - static void* - createSingletonInstance() - { - static InstanceHolder storage; // note: the singleton(s) live here - return storage.buildInstance(); - } - - - template - friend InstanceConstructor buildSingleton(); - - }; - - - - /** - * DSL-style marker function for client code - * to configure the usage of a specific subclass. - * Typically this function is used right within the - * Constructor call for lib::Depend; this allows to - * confine the actual service implementation class - * to a single compilation unit, without the need - * for clients of the respective service to know - * the actual concrete implementation class - */ - template - inline DependencyFactory::InstanceConstructor - buildSingleton() - { - return & DependencyFactory::createSingletonInstance; - } - - -} // namespace lib -#endif diff --git a/src/lib/test/depend-4test.hpp b/src/lib/test/depend-4test.hpp deleted file mode 100644 index ebfa3468c..000000000 --- a/src/lib/test/depend-4test.hpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - DEPEND4TEST.hpp - inject test mock singletons and dependencies - - - Copyright (C) Lumiera.org - 2013, Hermann Vosseler - - This program is free software; you can redistribute it and/or - modify it under the terms of the GNU General Public License as - published by the Free Software Foundation; either version 2 of - the License, or (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software - Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. - -*/ - - -/** @file depend-4test.hpp - ** test support code to inject mock variants of dependencies - */ - - - -#ifndef LIB_TEST_DEPEND_4TEST_H -#define LIB_TEST_DEPEND_4TEST_H - - -#include "lib/depend.hpp" -#include "lib/meta/duck-detector.hpp" - -#include - - -namespace lib { -namespace test{ - - namespace { ///< details: inject a mock automatically in place of a singleton - - using lib::meta::enable_if; - using lib::meta::Yes_t; - using lib::meta::No_t; - - /** - * Metafunction: does the Type in question - * give us a clue about what service interface to use? - */ - template - class defines_ServiceInterface - { - META_DETECT_NESTED (ServiceInterface); - - public: - enum{ value = HasNested_ServiceInterface::value - }; - }; - - - /** - * Policy-Trait: determine the access point to install the mock. - * @note either the mock service implementation needs to provide - * explicitly a typedef for the ServiceInterface, or we're - * just creating a separate new instance of the singleton service, - * while shadowing (but not destroying) the original instance. - */ - template - struct ServiceInterface - { - typedef I Type; - }; - - - template - struct ServiceInterface >> - { - typedef typename MOCK::ServiceInterface Type; - }; - - }//(End) mock injection details - - - - - - /** - * Scoped object for installing/deinstalling a mocked service automatically. - * Placing a suitably specialised instance of this template into a local scope - * will inject the corresponding mock installation and remove it when the - * control flow leaves this scope. - * @param TYPE the concrete mock implementation type to inject. It needs to - * be default constructible. If TYPE is a subclass of the service interface, - * it needs to expose a typedef \c ServiceInterface - */ - template - struct Depend4Test - : util::NonCopyable - { - typedef typename ServiceInterface::Type Interface; - - std::unique_ptr mock_; - Interface* shadowedOriginal_; - - Depend4Test() - : mock_{new TYPE} - , shadowedOriginal_{Depend::injectReplacement (mock_.get())} - { } - - ~Depend4Test() - { - ENSURE (mock_); - Depend::injectReplacement (shadowedOriginal_); - } - }; - - -}} // namespace lib::test -#endif