design-patterns - 如何使用 Ninject 等 IoC 实现 [GoF] 风格的抽象工厂模式

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

摘要

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

考虑到以下 API,我试图设置我的 IoC(在本例中为 Ninject)来检索通过 IConfigurationFactory 配置的 Configuration 对象。该配置存储一个 IFactory 实例,其实现由 ProductFamily 类型的运行时参数确定。之后,配置中工厂创建的产品类型应始终与请求的 ProductFamily 相匹配。由 Component 类组成的子图为每个 Configuration 保存相同的 IFactory

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您的时间和精力 最好的问候

伊萨亚斯

最佳答案

以下替代方案通过了所有测试,同时保持相当通用。 绑定(bind)定义了所有配置依赖项。 ninject 特定的唯一非绑定(bind)代码是 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 每个配置的单例”。 在上面的示例代码中,这当然不是必需的,因为工厂实例并不特定于配置。如果工厂特定于某个配置,请为每个工厂创建一个绑定(bind),并使用 .WhenProductFamily(..) 绑定(bind)扩展来确保注入(inject)正确的工厂。

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

关于design-patterns - 如何使用 Ninject 等 IoC 实现 [GoF] 风格的抽象工厂模式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/20954481/

相关文章:

c# - 使用 Ninject,我可以在不公开具体类的情况下从接口(interface)创建实例吗?

.net - StructureMap 单例因参数而异?

.net - 在 ASP.MVC 3 中使用 DI 和 IoC 如何设置我的 Controller 以正确解析新类型?

c# - ASP.NET MVP 注入(inject)服务依赖

c++ - 使用什么设计模式?

c# - 提供重定向层的 C# 类的命名约定

c# - IDisposable 对象的依赖注入(inject)和生命周期

c# - 抽象工厂模式和属性

design-patterns - 寻找处理 'reference data' 的设计模式

asp.net-mvc - 我应该为每个模型创建一个接口(interface)吗?