#pragma region CPL License /* Nuclex Native Framework Copyright (C) 2002-2023 Nuclex Development Labs This library is free software; you can redistribute it and/or modify it under the terms of the IBM Common Public License as published by the IBM Corporation; either version 1.0 of the License, or (at your option) any later version. This library 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 IBM Common Public License for more details. You should have received a copy of the IBM Common Public License along with this library */ #pragma endregion // CPL License #ifndef NUCLEX_SUPPORT_SERVICES_LAZYSERVICEINJECTOR_H #define NUCLEX_SUPPORT_SERVICES_LAZYSERVICEINJECTOR_H #include "Nuclex/Support/Config.h" #include "Nuclex/Support/Services/ServiceContainer.h" #include // for std::map (storing services by std::type_info) namespace Nuclex { namespace Support { namespace Services { // ------------------------------------------------------------------------------------------- // /// The maximum number of constructor arguments that can be injected /// /// Increasing this value will result in (slightly) slower compiles. Though you might /// want to reconsider your design if a single type consumes more than 8 services ;) /// static constexpr std::size_t MaximumConstructorArgumentCount = 8; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Support::Services #include "Nuclex/Support/Services/IntegerSequence.inl" #include "Nuclex/Support/Services/Checks.inl" #include "Nuclex/Support/Services/ConstructorSignature.inl" #include "Nuclex/Support/Services/ConstructorSignatureDetector.inl" #include "Nuclex/Support/Services/ServiceFactory.inl" namespace Nuclex { namespace Support { namespace Services { // ------------------------------------------------------------------------------------------- // /// Binds services and initializes them via constructor injection /// /// This is a very simplified dependency injector that only supports global services /// stored in shared_ptrs. /// class NUCLEX_SUPPORT_TYPE LazyServiceInjector : public ServiceProvider { #pragma region class TypeInfoComparer /// Compares instances of std::type_info private: class TypeInfoComparer { /// Determines the relationship of two std::type_info instances /// Type info to compare on the left side /// Type info to compare on the right side /// True if the left side comes before the right side public: bool operator()(const std::type_info *left, const std::type_info *right) const { return left->before(*right) != 0; } }; #pragma endregion // class TypeInfoComparer #pragma region class BindSyntax /// Provides the syntax for the fluent Bind() method /// Service for which a binding will be set up public: template class BindSyntax { friend LazyServiceInjector; /// Initializes the syntax helper for binding services /// Service injector on which services will be bound protected: BindSyntax(LazyServiceInjector &serviceInjector) : serviceInjector(serviceInjector) {} /// Binds the service to a constructor-injected provider /// Implementation of the service to use /// /// This binds the service to the specified service implementation /// public: template void To() { typedef Private::DetectConstructorSignature ConstructorSignature; // Verify that the implementation actually implements the service static_assert( std::is_base_of::value, "Implementation must inherit from the service interface" ); // Also verify that the implementation's constructor can be injected constexpr bool implementationHasInjectableConstructor = !std::is_base_of< Private::InvalidConstructorSignature, ConstructorSignature >::value; static_assert( implementationHasInjectableConstructor, "Implementation must have a constructor that can be dependency-injected " "(either providing a default constructor or using only std::shared_ptr arguments)" ); // Implementation looks injectable, add the service factory method to the map const std::type_info &serviceTypeInfo = typeid(TService); this->serviceInjector.factories.insert( ServiceFactoryMap::value_type( &serviceTypeInfo, [](const ServiceProvider &serviceProvider) { typedef Private::ServiceFactory Factory; return std::any( std::static_pointer_cast(Factory::CreateInstance(serviceProvider)) ); } ) ); } /// Binds the service to a factory method or functor used to create it /// Type of value returned by the service factory /// Service factory method used to create the service public: template< typename TResult, std::shared_ptr(*TMethod)(const ServiceProvider &) > void ToFactoryMethod() { // Verify that whatever the factory method returns implements the service static_assert( std::is_base_of::value, "Factory method must return either the service type or one that " "inherits from it" ); // Method does provide the service, add it to the map const std::type_info &serviceTypeInfo = typeid(TService); this->serviceInjector.factories.insert( ServiceFactoryMap::value_type( &serviceTypeInfo, [](const ServiceProvider &serviceProvider) { return std::any(std::static_pointer_cast(TMethod(serviceProvider))); } ) ); } /// Binds the service to a factory method or functor used to create it /// Service factory method used to create the service public: template< std::shared_ptr(*TMethod)(const ServiceProvider &) > void ToFactoryMethod() { // Method does provide the service, add it to the map const std::type_info &serviceTypeInfo = typeid(TService); this->serviceInjector.factories.insert( ServiceFactoryMap::value_type( &serviceTypeInfo, [](const ServiceProvider &serviceProvider) { return std::any(TMethod(serviceProvider)); } ) ); } /// Binds the service to an already constructed service instance /// Instance that will be returned for the service public: void ToInstance(const std::shared_ptr &instance) { const std::type_info &serviceTypeInfo = typeid(TService); this->serviceInjector.instances.insert( ServiceInstanceMap::value_type(&serviceTypeInfo, std::any(instance)) ); } /// Assumes that the service and its implementation are the same type /// /// For trivial services that don't have an interface separate from their implementation /// class (or when you just have to provide some implementation everywhere), /// use this method to say that the service type is a non-abstract class and /// should be created directly. /// public: void ToSelf() { typedef Private::DetectConstructorSignature ConstructorSignature; constexpr bool serviceHasInjectableConstructor = !std::is_base_of< Private::InvalidConstructorSignature, ConstructorSignature >::value; static_assert( serviceHasInjectableConstructor, "Self-bound service must not be abstract and requires a constructor " "that can be dependency-injected (either providing a default constructor or " "using only std::shared_ptr arguments)" ); // Service looks injectable, add the service factory method to the map const std::type_info &serviceTypeInfo = typeid(TService); this->serviceInjector.factories.insert( ServiceFactoryMap::value_type( &serviceTypeInfo, [](const ServiceProvider &serviceProvider) { typedef Private::ServiceFactory Factory; return std::any(Factory::CreateInstance(serviceProvider)); } ) ); } /// Service injector to which the binding will be added private: LazyServiceInjector &serviceInjector; }; #pragma endregion // class BindSyntax /// Initializes a new service injector public: NUCLEX_SUPPORT_API LazyServiceInjector() = default; /// Destroys the service injector and frees all resources public: NUCLEX_SUPPORT_API virtual ~LazyServiceInjector() = default; /// Binds a provider to the specified service /// A syntax through which the provider to be bound can be selected public: template BindSyntax Bind() { return BindSyntax(*this); } // Unhide the templated Get method from the service provider using ServiceProvider::Get; // Unhide the templated TryGet method fro mthe service provider using ServiceProvider::TryGet; /// Creates a new instance of the specified service /// Type of service that will be created /// A new instance of the requested service public: template std::shared_ptr Create() const { const std::type_info &serviceTypeInfo = typeid(TService); std::shared_ptr newServiceInstance( std::any_cast>(Create(serviceTypeInfo)) ); return newServiceInstance; } /// Looks up the specified service /// Type of service that will be looked up /// /// The specified service as a shared_ptr wrapped in an /// protected: NUCLEX_SUPPORT_API const std::any &Get( const std::type_info &serviceType ) const override; /// Tries to look up the specified service /// Type of service that will be looked up /// An std::any containing the service, if found, or an empty std::any protected: NUCLEX_SUPPORT_API const std::any &TryGet( const std::type_info &serviceType ) const override; /// Creates the specified service /// Type of service that will be created /// /// The specified service as a shared_ptr wrapped in an /// protected: NUCLEX_SUPPORT_API std::any Create( const std::type_info &serviceType ) const; /// Delegate for a factory method that creates a service private: typedef std::any(*CreateServiceFunction)(const ServiceProvider &); /// Map of factories to create different services private: typedef std::map< const std::type_info *, CreateServiceFunction, TypeInfoComparer > ServiceFactoryMap; /// Map of services permanently stored in the container private: typedef std::map< const std::type_info *, std::any, TypeInfoComparer > ServiceInstanceMap; // These are both mutable. Reasoning: the service injector acts as if all services // already existed, so while services may get constructed as a result of requesting // them, to the caller there's no different between an already provided service // and one that is constructed during the Get() call. /// Factory methods to construct the various services private: mutable ServiceFactoryMap factories; /// Stores services that have already been initialized private: mutable ServiceInstanceMap instances; }; // ------------------------------------------------------------------------------------------- // }}} // namespace Nuclex::Support::Services #endif // NUCLEX_SUPPORT_SERVICES_LAZYSERVICEINJECTOR_H