diff --git a/CHANGES.md b/CHANGES.md index d5bc463..a60cf49 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,5 +1,8 @@ Poodinis Changelog ================== +**Version 2.1.0** +* ADD option for registering a class by concrete type when registering that class by supertype. + **Version 2.0.0** This version introduces changes which might be incompatible with your current codebase * CHANGE dependency container to be synchronized. Sharing a dependency container between threads is now possible. diff --git a/README.md b/README.md index cc4793d..d254f16 100644 --- a/README.md +++ b/README.md @@ -74,6 +74,13 @@ auto exampleClassInstance = dependencies.resolve!ExampleClass; auto exampleClassInstance2 = dependencies.resolve!ExampleInterface; assert(exampleClassInstance !is exampleClassInstance2); ``` +You can solve this by adding the ADD_CONCRETE_TYPE_REGISTRATION option when registering: +```d +dependencies.register!(ExampleInterface, ExampleClass)(RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION); +auto exampleClassInstance = dependencies.resolve!ExampleClass; +auto exampleClassInstance2 = dependencies.resolve!ExampleInterface; +assert(exampleClassInstance is exampleClassInstance2); +``` ###Dependency scopes With dependency scopes, you can control how a dependency is resolved. The scope determines which instance is returned, be it the same each time or a new one. The following scopes are available: diff --git a/source/poodinis/container.d b/source/poodinis/container.d index 0a16d4c..1a9f2dd 100644 --- a/source/poodinis/container.d +++ b/source/poodinis/container.d @@ -33,6 +33,41 @@ class ResolveException : Exception { } } +/** + * Exception thrown when errors occur while registering a type in a dependency container. + */ +class RegistrationException : Exception { + this(string message, TypeInfo registrationType) { + super(format("Exception while registering type %s: %s", registrationType.toString(), message)); + } +} + +/** + * Options which influence the process of registering dependencies + */ +public enum RegistrationOptions { + /** + * When registering a type by its supertype, providing this option will also register + * a linked registration to the type itself. + * + * This allows you to resolve that type both by super type and concrete type using the + * same registration scope (and instance managed by this scope). + * + * Examples: + * --- + * class Cat : Animal { ... } + * + * container.register!(Animal, Cat)(RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION); + * + * auto firstCat = container.resolve!(Animal, Cat); + * auto secondCat = container.resolve!Cat; + * + * assert(firstCat is secondCat); + * --- + */ + ADD_CONCRETE_TYPE_REGISTRATION +} + /** * The dependency container maintains all dependencies registered with it. * @@ -63,11 +98,7 @@ synchronized class DependencyContainer { * Register and resolve a class by concrete type: * --- * class Cat : Animal { ... } - * * container.register!Cat; - * - * container.resolve!Cat; - * container.resolve!(Animal, Cat); // Error! dependency is not registered by super type. * --- * * See_Also: singleInstance, newInstance, existingInstance @@ -88,16 +119,12 @@ synchronized class DependencyContainer { * Register and resolve by super type * --- * class Cat : Animal { ... } - * * container.register!(Animal, Cat); - * - * container.resolve!(Animal, Cat); - * container.resolve!Cat; // Error! dependency is not registered by concrete type. * --- * - * See_Also: singleInstance, newInstance, existingInstance + * See_Also: singleInstance, newInstance, existingInstance, RegistrationOptions */ - public Registration register(SuperType, ConcreteType : SuperType)() { + public Registration register(SuperType, ConcreteType : SuperType, RegistrationOptionsTuple...)(RegistrationOptionsTuple options) { TypeInfo registeredType = typeid(SuperType); TypeInfo_Class concreteType = typeid(ConcreteType); @@ -112,10 +139,30 @@ synchronized class DependencyContainer { auto newRegistration = new AutowiredRegistration!ConcreteType(registeredType, this); newRegistration.singleInstance(); + + if (hasOption(options, RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION)) { + static if (!is(SuperType == ConcreteType)) { + auto concreteTypeRegistration = register!ConcreteType; + concreteTypeRegistration.linkTo(newRegistration); + } else { + throw new RegistrationException("Option ADD_CONCRETE_TYPE_REGISTRATION cannot be used when registering a concrete type registration", concreteType); + } + } + registrations[registeredType] ~= cast(shared(Registration)) newRegistration; return newRegistration; } + private bool hasOption(RegistrationOptionsTuple...)(RegistrationOptionsTuple options, RegistrationOptions option) { + foreach(presentOption ; options) { + if (presentOption == option) { + return true; + } + } + + return false; + } + private Registration getExistingRegistration(TypeInfo registrationType, TypeInfo qualifierType) { auto existingCandidates = registrationType in registrations; if (existingCandidates) { diff --git a/test/poodinis/containertest.d b/test/poodinis/containertest.d index 1fcc9be..bbf096a 100644 --- a/test/poodinis/containertest.d +++ b/test/poodinis/containertest.d @@ -426,4 +426,21 @@ version(unittest) { assert(expectedTestClass is actualTestClass, "Instance resolved in main thread is not the one resolved in thread"); } + + // Test registering type with option ADD_CONCRETE_TYPE_REGISTRATION + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + container.register!(TestInterface, TestClass)(RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION); + + auto firstInstance = container.resolve!TestInterface; + auto secondInstance = container.resolve!TestClass; + + assert(firstInstance is secondInstance); + } + + // Test registering concrete type with option ADD_CONCRETE_TYPE_REGISTRATION + unittest { + shared(DependencyContainer) container = new DependencyContainer(); + assertThrown!RegistrationException(container.register!(TestClass, TestClass)(RegistrationOptions.ADD_CONCRETE_TYPE_REGISTRATION)); + } }