摘要
当设计需要像 [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/