如何使用IoC像Ninject来实现[GoF]的抽象工厂模式 [英] How to implement an [GoF]-ish Abstract Factory Pattern using an IoC like Ninject

查看:246
本文介绍了如何使用IoC像Ninject来实现[GoF]的抽象工厂模式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

摘要



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



鉴于以下API,我正在尝试设置IoC(Ninject在这种情况下)来检索通过 IConfigurationFactory 配置的配置对象。该配置存储一个 IFactory 实例,它们由 ProductFamily 类型的运行时参数确定。之后,厂商在配置中创建的产品类型应始终与请求的 ProductFamily 匹配。由组件组成的子图包含相同的 IFactory 每个配置

  public enum ProductFamily {A,B} 
public interface IProduct1 {}
public接口IProduct2 {}
public interface IFactory
{
IProduct1 CreateProduct1();
IProduct2 CreateProduct2();
}
public class配置
{
public readonly IFactory factory;
public readonly组件组件;
public配置(IFactory工厂,组件组件)
{
this.factory = factory;
this.component = component;
}
}
public class Component
{
public IFactory factory;
public Component(IFactory factory){this.factory = factory;
}
public interface IConfigurationFactory
{
配置CreateConfiguration(ProductFamily系列);
}



测试



为了澄清预期的行为,我添加了我的测试代码writte在vstest。但是正式的一些补充,感谢@BatterBackupUnit提出这些细节:




  • 工厂只需要 ProductFamily 作为在实现之间选择的参数,没有其他

  • 每个配置及其后续对象组件,共享相同的工厂实例



所以我希望这有帮助:)

  [TestMethod] 
public void TestMethod1()
{
var configFac = ComposeConfigurationFactory );
//创建运行时依赖配置
var configA = configFac.CreateConfiguration(ProductFamily.A);
var configB = configFac.CreateConfiguration(ProductFamily.B);

//检查工厂的配置
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));

//所有可能的配置子项应该共享同一个工厂
Assert.IsTrue(configA.factory == configA.component.factory);
//不同的配置不应该共享同一个工厂
var configA2 = configFac.CreateConfiguration(ProductFamily.A);
Assert.IsTrue(configA.factory!= configA2.factory);
}

这个问题已经解决了,所以我删除了所有不必要的绒毛。 p>

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



Isaias

解决方案

以下替代方法传递所有测试,同时保持相当通用。
绑定定义所有配置依赖关系。唯一不具体的非绑定代码是IConfigurationFactory,它将必要的配置信息(=> ProductFamily)放在ninject上下文中。



您将需要以下nuget程序包编译:




  • 流利的断言

  • Ninject

  • Ninject.Extensions.ContextPreservation

  • Ninject.Extensions.Factory

  • Ninject.Extensions.NamedScope



这是代码:

  using System.Linq; 
使用FluentAssertions;
使用Ninject;
使用Ninject.Activation;
使用Ninject.Extensions.Factory;
使用Ninject.Extensions.NamedScope;
使用Ninject.Modules;
使用Ninject.Parameters;
使用Ninject.Planning.Targets;
使用Ninject.Syntax;

public class Program
{
private static void Main(string [] args)
{
var kernel = new StandardKernel();
kernel.Load< AbstractFactoryModule>();

var configFac = kernel.Get&ConfigurationFactory>();

//创建运行时依赖配置
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配置
{
公开readonly IFactory工厂;
public readonly组件组件;
public配置(IFactory工厂,组件组件)
{
this.factory = factory;
this.component = component;
}
}
public class Component
{
public IFactory factory;
public Component(IFactory factory){this.factory = factory; }
}

public interface IConfigurationFactory
{
配置CreateConfiguration(ProductFamily系列);
}

public class ConfigurationFactory:IConfigurationFactory
{
private readonly IResolutionRoot resolutionRoot;

public ConfigurationFactory(IResolutionRoot resolutionRoot)
{
this.resolutionRoot = resolutionRoot;
}

public配置CreateConfiguration(ProductFamily系列)
{
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>()。到< Product1A>()
.WhenProductFamiliy(ProductFamily.A);
this.Bind< IProduct1>()。到< Product1B>()
.WhenProductFamiliy(ProductFamily.B);
}
}

public static class AbstractFactoryBindingExtensions
{
public static IBindingInNamedWithOrOnSyntax< T>当产品方法< T>(此IBindingWhenInNamedWithOrnnynthesis< T> binding,ProductFamily productFamily)
{
return binding
.When(x => x.Parameters.OfType< AbstractFactoryConfigurationParameter>() ).ProductFamily == productFamily);
}
}

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



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


Abstract

When the design requires an "Abstract Factory Pattern" like stated by the [GoF] including several products and over some product families, then setting up an IoC can become a bit tricky. Especially when the specific factory implementations need to be dispatched by runtime parameters and shared among some subsequent components.

Given the follwing API, i was trying to set up my IoC (Ninject in this case) to retrieve Configuration objects configured through a IConfigurationFactory. The configuration stores an IFactory instance whoose implementation is determined by a runtime parameter of type ProductFamily. Afterwards the product types created by the factory inside of the configuration should always match the requested ProductFamily. The subgraph, consisting of the Component class holds the same IFactory per 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);
}

Tests

To clarify the intended behaviour i have added my test code writte in vstest. But forehand some additions, thanks to @BatterBackupUnit for asking these nitty details:

  • The factories do only need the ProductFamily as a parameter to choose between the implementations, nothing else
  • Every Configuration and its subsequent objects like the Component, share the same factory instance

So i hope this helps :)

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

This qestion has already been solved therefore i removed all the unnecessary fluff.

Thanks to @BatteryBackupUnit for your time and effort Best regards

Isaias

解决方案

The following alternative passes all your tests while remaining fairly generic. The bindings define all configuration dependencies. The only non-binding code which is ninject specific is the IConfigurationFactory which puts the necessary configuration information (=>ProductFamily) on the ninject context.

You will need the following nuget packages to make this code compile:

  • Fluent Assertions
  • Ninject
  • Ninject.Extensions.ContextPreservation
  • Ninject.Extensions.Factory
  • Ninject.Extensions.NamedScope

Here's the code:

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

Please note that I am not convinced that the Named Scope is necessary for your use case. The named scope ensures that there is only one instance of a type (here: the IFactory) per scope (here: the configuration instance). So you basically get an "IFactory singleton per configuration". In the above example code it certainly is not required as the factory instances are not specific to configuration. If the factories are specific to a configuration create a binding for each and also use the .WhenProductFamily(..) binding extension to make sure the correct factory gets injected.

Also note that you can make the AbstractFactoryConfigurationParameter and the .WhenProductFamily(..) extension more generic so you can reuse it for multiple different abstract factories.

这篇关于如何使用IoC像Ninject来实现[GoF]的抽象工厂模式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆