How to implement an [GoF]-ish Abstract Factory Pattern using an IoC like Ninject

老子叫甜甜 提交于 2019-11-29 12:07:49

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:

  • Fluent Assertions
  • Ninject
  • Ninject.Extensions.ContextPreservation
  • Ninject.Extensions.Factory
  • Ninject.Extensions.NamedScope

Here's the code:

using System.Linq;
using FluentAssertions;
using Ninject;
using Ninject.Activation;
using Ninject.Extensions.Factory;
using Ninject.Extensions.NamedScope;
using Ninject.Modules;
using Ninject.Parameters;
using Ninject.Planning.Targets;
using Ninject.Syntax;

public class Program
{
    private static void Main(string[] args)
    {
        var kernel = new StandardKernel();
        kernel.Load<AbstractFactoryModule>();

        var configFac = kernel.Get<ConfigurationFactory>();

        // create runtime dependent configs
        var configA = configFac.CreateConfiguration(ProductFamily.A);
        var configB = configFac.CreateConfiguration(ProductFamily.B);

        configA.factory.CreateProduct1().Should().BeOfType<Product1A>();
        configB.factory.CreateProduct1().Should().BeOfType<Product1B>();

        configA.component.factory.Should().Be(configA.factory);

        configA.factory.Should().NotBe(configB.factory);
    }
}

public enum ProductFamily { A, B }
public interface IProduct1 { }
public interface IFactory
{
    IProduct1 CreateProduct1();
}

public class Product1A : IProduct1 { }
public class Product1B : IProduct1 { }

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);
}

public class ConfigurationFactory : IConfigurationFactory
{
    private readonly IResolutionRoot resolutionRoot;

    public ConfigurationFactory(IResolutionRoot resolutionRoot)
    {
        this.resolutionRoot = resolutionRoot;
    }

    public Configuration CreateConfiguration(ProductFamily family)
    {
        return this.resolutionRoot.Get<Configuration>(new AbstractFactoryConfigurationParameter(family));
    }
}

public class AbstractFactoryConfigurationParameter : IParameter
{
    private readonly ProductFamily parameterValue;

    public AbstractFactoryConfigurationParameter(ProductFamily parameterValue)
    {
        this.parameterValue = parameterValue;
    }

    public ProductFamily ProductFamily
    {
        get { return this.parameterValue; }
    }

    public string Name
    {
        get { return this.GetType().Name; }
    }

    public bool ShouldInherit
    {
        get { return true; }
    }

    public object GetValue(IContext context, ITarget target)
    {
        return this.parameterValue;
    }

    public bool Equals(IParameter other)
    {
        return this.GetType() == other.GetType();
    }
}

public class AbstractFactoryModule : NinjectModule
{
    private const string ConfigurationScopeName = "ConfigurationScope";

    public override void Load()
    {
        this.Bind<IConfigurationFactory>().To<ConfigurationFactory>();
        this.Bind<Configuration>().ToSelf()
            .DefinesNamedScope(ConfigurationScopeName);
        this.Bind<IFactory>().ToFactory()
            .InNamedScope(ConfigurationScopeName);
        this.Bind<IProduct1>().To<Product1A>()
            .WhenProductFamiliy(ProductFamily.A);
        this.Bind<IProduct1>().To<Product1B>()
            .WhenProductFamiliy(ProductFamily.B);
    }
}

public static class AbstractFactoryBindingExtensions
{
    public static IBindingInNamedWithOrOnSyntax<T> WhenProductFamiliy<T>(this IBindingWhenInNamedWithOrOnSyntax<T> binding, ProductFamily productFamily)
    {
        return binding
            .When(x => x.Parameters.OfType<AbstractFactoryConfigurationParameter>().Single().ProductFamily == productFamily);
    }
}

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.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!