Abstract
When the design requires an "Abstract Factory Pattern" like stated by the [GoF] including several products and over some product families, then setting up an IoC can become a bit tricky. Especially when the specific factory implementations need to be dispatched by runtime parameters and shared among some subsequent components.
Given the follwing API, i was trying to set up my IoC (Ninject in this case) to retrieve Configuration
objects configured through a IConfigurationFactory
. The configuration stores an IFactory
instance whoose implementation is determined by a runtime parameter of type ProductFamily
. Afterwards the product types created by the factory inside of the configuration should always match the requested ProductFamily
. The subgraph, consisting of the Component
class holds the same IFactory
per Configuration
.
public enum ProductFamily { A, B }
public interface IProduct1 { }
public interface IProduct2 { }
public interface IFactory
{
IProduct1 CreateProduct1();
IProduct2 CreateProduct2();
}
public class Configuration
{
public readonly IFactory factory;
public readonly Component component;
public Configuration(IFactory factory, Component component)
{
this.factory = factory;
this.component = component;
}
}
public class Component
{
public IFactory factory;
public Component(IFactory factory) { this.factory = factory; }
}
public interface IConfigurationFactory
{
Configuration CreateConfiguration(ProductFamily family);
}
Tests
To clarify the intended behaviour i have added my test code writte in vstest. But forehand some additions, thanks to @BatterBackupUnit for asking these nitty details:
- The factories do only need the
ProductFamily
as a parameter to choose between the implementations, nothing else - Every
Configuration
and its subsequent objects like theComponent
, share the same factory instance
So i hope this helps :)
[TestMethod]
public void TestMethod1()
{
var configFac = ComposeConfigurationFactory();
// create runtime dependent configs
var configA = configFac.CreateConfiguration(ProductFamily.A);
var configB = configFac.CreateConfiguration(ProductFamily.B);
// check the configuration of the factories
Assert.IsInstanceOfType(configA.factory.CreateProduct1(), typeof(Product1A));
Assert.IsInstanceOfType(configB.factory.CreateProduct1(), typeof(Product1B));
Assert.IsInstanceOfType(configA.factory.CreateProduct2(), typeof(Product2A));
Assert.IsInstanceOfType(configB.factory.CreateProduct2(), typeof(Product2B));
// all possible children of the configuration should share the same factory
Assert.IsTrue(configA.factory == configA.component.factory);
// different configurations should never share the same factory
var configA2 = configFac.CreateConfiguration(ProductFamily.A);
Assert.IsTrue(configA.factory != configA2.factory);
}
This qestion has already been solved therefore i removed all the unnecessary fluff.
Thanks to @BatteryBackupUnit for your time and effort Best regards
Isaias
The following alternative passes all your tests while remaining fairly generic. The bindings define all configuration dependencies. The only non-binding code which is ninject specific is the IConfigurationFactory which puts the necessary configuration information (=>ProductFamily) on the ninject context.
You will need the following nuget packages to make this code compile:
Here's the code:
Please note that I am not convinced that the Named Scope is necessary for your use case. The named scope ensures that there is only one instance of a type (here: the IFactory) per scope (here: the configuration instance). So you basically get an "
IFactory
singleton per configuration". In the above example code it certainly is not required as the factory instances are not specific to configuration. If the factories are specific to a configuration create a binding for each and also use the.WhenProductFamily(..)
binding extension to make sure the correct factory gets injected.Also note that you can make the
AbstractFactoryConfigurationParameter
and the.WhenProductFamily(..)
extension more generic so you can reuse it for multiple different abstract factories.