如何使用类似IoC的Ninject来实现[GoF]风格的抽象工厂模式



摘要

当设计需要像[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为您付出的时间和精力向致以最良好的问候

Isaias

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

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

  • Fluent断言
  • 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(..)扩展更通用,这样您就可以在多个不同的抽象工厂中重用它。

最新更新