2

抽象的

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

鉴于以下 API,我试图设置我的 IoC(在本例中为 Ninject)以检索Configuration通过IConfigurationFactory. 配置存储一个IFactory实例,其实现由 type 的运行时参数确定ProductFamily。之后,工厂在配置中创建的产品类型应始终与请求的匹配ProductFamily。由类组成的子图Component具有相同的IFactoryper 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 您的时间和精力最好的问候

伊萨亚斯

4

1 回答 1

3

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

您将需要以下 nuget 包来编译此代码:

  • 流利的断言
  • 忍者
  • 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(..)扩展更通用,以便您可以将其重用于多个不同的抽象工厂。

于 2014-01-07T09:46:48.063 回答