如何使用像Ninject这样的IoC实现[GoF] -ish抽象工厂模式

时间:2014-01-06 16:27:06

标签: design-patterns ninject inversion-of-control named-scope abstract-factory

摘要

当设计需要像[GoF]所述的“抽象工厂模式”时,包括几个产品和一些产品系列,那么设置IoC可能会变得有点棘手。特别是当需要通过运行时参数调度特定工厂实现并在某些后续组件之间共享时。

鉴于以下API,我试图设置我的IoC(在这种情况下为Ninject)来检索通过Configuration配置的IConfigurationFactory个对象。配置存储IFactory实例,其实现由类型为ProductFamily的运行时参数确定。之后,工厂在配置内创建的产品类型应始终与请求的ProductFamily匹配。由Component类组成的子图与IFactory每个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);
}

测试

为了澄清我在vstest中添加测试代码写入的预期行为。但正确的一些补充,感谢@BatterBackupUnit询问这些细节:

  • 工厂只需要ProductFamily作为参数来在实现之间进行选择,没有别的
  • 每个Configuration及其后续对象(如Component)共享同一个工厂实例

所以我希望这会有所帮助:)

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

这个问题已经解决,因此我删除了所有不必要的毛病。

感谢@BatteryBackupUnit您的时间和精力 最好的问候

伊萨亚斯阿

1 个答案:

答案 0 :(得分:3)

以下替代方案通过了所有测试,同时保持相当通用。 绑定定义所有配置依赖性。唯一特定于ninject的非绑定代码是IConfigurationFactory,它将必要的配置信息(=> ProductFamily)放在ninject上下文中。

您需要以下nuget包才能编译此代码:

  • 流利的断言
  • Ninject
  • Ninject.Extensions.ContextPreservation
  • Ninject.Extensions.Factory
  • Ninject.Extensions.NamedScope

以下是代码:

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

请注意,我不相信您的用例需要命名范围。命名范围确保每个范围只有一个类型实例(此处为:IFactory)(此处为:配置实例)。所以你基本上得到“每个配置IFactory单身”。 在上面的示例代码中,它当然不是必需的,因为工厂实例不是特定于配置的。如果工厂特定于配置,则为每个工厂创建绑定,并使用.WhenProductFamily(..)绑定扩展来确保注入正确的工厂。

另请注意,您可以使AbstractFactoryConfigurationParameter.WhenProductFamily(..)扩展名更具通用性,以便您可以将其重复用于多个不同的抽象工厂。