使用通过DI容器注入的抽象工厂

时间:2016-03-24 13:41:41

标签: c# .net dependency-injection service-locator abstract-factory

我在一个具体的例子中对依赖注入实现感到困惑。

假设我们有一个SomeClass类,它具有IClassX类型的依赖关系。

public class SomeClass
{
     public SomeClass(IClassX dependency){...}
}

创建IClassX接口的具体实现取决于运行时参数N。

使用给定的构造函数,我无法配置DI容器(使用Unity),因为我不知道在运行时将使用哪种IClassX实现。 Mark Seemann在他的着作Dependency Injection In .Net中建议我们应该使用Abstract Factory作为注入参数。

现在我们有SomeAbstractFactory,它基于运行时参数runTimeParam返回IClassX的实现。

public class SomeAbstractFactory
{
     public SomeAbstractFactory(){ }

     public IClassX GetStrategyFor(int runTimeParam)
     {
         switch(runTimeParam)
         {
             case 1: return new ClassX1();
             case 2: return new ClassX2();
               default : return new ClassDefault();
         }
     }
}

SomeClass现在接受ISomeAbstractFactory作为注入参数:

public class SomeClass
{
     public SomeClass(ISomeAbstractFactory someAbstractfactory){...}
}

那没关系。我们只有一个组合根,我们在其中创建对象图。我们配置Unity容器以将SomeAbstractFactory注入SomeClass。

但是,我们假设类ClassX1和ClassX2有自己的依赖项:

public class ClassX1 : IClassX
{
    public ClassX1(IClassA, IClassB) {...}
}

public class ClassX2 : IClassX
{
    public ClassX2(IClassA, IClassC, IClassD) {...}
}

如何解决IClassA,IClassB,IClassC和IClassD依赖关系?

1。通过SomeAbstractFactory构造函数注入

我们可以像这样向SomeAbstractFactory注入IClassA,IClassB,IClassC和IClassD的具体实现:

public class SomeAbstractFactory
{
     public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD)
     {...}
     ...
}

Unity容器将在初始组合根中使用,然后使用穷人的DI根据参数runTimeParam返回具体的ClassX1或ClassX2

public class SomeAbstractFactory
{
     public SomeAbstractFactory(IClassA classA, IClassB classB, IClassC classC, IClassD classD){...}

     public IClassX GetStrategyFor(int runTimeParam)
     {
         switch(runTimeParam)
         {
             case 1: return new ClassX1(classA, classB);
             case 2: return new ClassX2(classA, classC, classD);
               default : return new ClassDefault();
         }
     }
}

这种方法存在问题:

  • SomeAbstractFactory知道不属于它的依赖项。
  • 更深的对象图需要更改SomeAbstractFactory构造函数和类实现
  • DI容器不会用于解决依赖关系,必须使用穷人的DI

2。显式调用DI容器

我们将使用DI容器解决它们,而不是“新建”ClassX1或ClassX2。

public class SomeAbstractFactory
{
     public SomeAbstractFactory(IUnityContainer container){...}

     public IClassX GetStrategyFor(int runTimeParam)
     {
         switch(runTimeParam)
         {
             case 1: return container.Resolve<IClassX>("x1");
             case 2: return container.Resolve<IClassX>("x2");
               default : return container.Resolve<IClassX>("xdefault");
         }
     }
}

这种方法存在问题:

  • DI容器被传递到SomeAbstractFactory
  • DI Resolve方法不仅用于组合根(ServiceLocator反模式)

还有其他更合适的方法吗?

1 个答案:

答案 0 :(得分:1)

下面的示例显示了如何使用Unity执行此操作。 This blog post使用温莎解释得更好一点。对于每个实现的基本概念完全相同,只是略有不同的实现。

我宁愿允许我的抽象工厂访问容器。我将抽象工厂视为一种防止依赖容器的方法 - 我的类只依赖于IFactory,因此它只是使用容器的工厂的实现。 Castle Windsor更进了一步 - 您为工厂定义了界面,但Windsor提供了实际的实现。但这是一个好兆头,相同的方法在两种情况下均有效,您不必更改工厂界面。

在下面的方法中,必要的是依赖于工厂的类传递一些参数,该参数允许工厂确定要创建的实例。工厂将其转换为字符串,容器将其与命名实例匹配。这种方法适用于Unity和Windsor。

这样做,取决于IFactory的类不知道工厂正在使用字符串值来查找正确的类型。在Windsor示例中,类将Address对象传递给工厂,工厂使用该对象根据地址的国家/地区确定要使用的地址验证器。没有其他班级,但工厂&#34;知道&#34;如何选择正确的类型。这意味着如果您切换到另一个容器,您唯一需要更改的是IFactory实现。依赖IFactory的任何内容都不得更改。

以下使用Unity的示例代码:

public interface IThingINeed
{}

public class ThingA : IThingINeed { }
public class ThingB : IThingINeed { }
public class ThingC : IThingINeed { }

public interface IThingINeedFactory
{
    IThingINeed Create(ThingTypes thingType);
    void Release(IThingINeed created);
}

public class ThingINeedFactory : IThingINeedFactory
{
    private readonly IUnityContainer _container;

    public ThingINeedFactory(IUnityContainer container)
    {
        _container = container;
    }

    public IThingINeed Create(ThingTypes thingType)
    {
        string dependencyName = "Thing" + thingType;
        if(_container.IsRegistered<IThingINeed>(dependencyName))
        {
            return _container.Resolve<IThingINeed>(dependencyName);
        }
        return _container.Resolve<IThingINeed>();
    }

    public void Release(IThingINeed created)
    {
        _container.Teardown(created);
    }
}

public class NeedsThing
{
    private readonly IThingINeedFactory _factory;

    public NeedsThing(IThingINeedFactory factory)
    {
        _factory = factory;
    }

    public string PerformSomeFunction(ThingTypes valueThatDeterminesTypeOfThing)
    {
        var thingINeed = _factory.Create(valueThatDeterminesTypeOfThing);
        try
        {
            //This is just for demonstration purposes. The method
            //returns the name of the type created by the factory
            //so you can tell that the factory worked.                
            return thingINeed.GetType().Name;
        }
        finally
        {
            _factory.Release(thingINeed);
        }
    }
}

public enum ThingTypes
{
    A, B, C, D
}

public class ContainerConfiguration
{
    public void Configure(IUnityContainer container)
    {
        container.RegisterType<IThingINeedFactory,ThingINeedFactory>(new InjectionConstructor(container));
        container.RegisterType<IThingINeed, ThingA>("ThingA");
        container.RegisterType<IThingINeed, ThingB>("ThingB");
        container.RegisterType<IThingINeed, ThingC>("ThingC");
        container.RegisterType<IThingINeed, ThingC>();
    }
}

这是一些单元测试。它们显示工厂在检查传递给IThingINeed函数的内容后返回正确类型的Create()

在这种情况下(可能适用也可能不适用)我也指定了一种类型作为默认类型。如果没有向容器注册任何与要求完全匹配的容器,那么它可以返回该默认值。该默认值也可以是没有行为的null实例。但所有这些选择都在工厂和容器配置中。

[TestClass]
public class UnitTest1
{
    private IUnityContainer _container;

    [TestInitialize]
    public void InitializeTest()
    {
       _container = new UnityContainer();
       var configurer = new ContainerConfiguration();
       configurer.Configure(_container);
    }

    [TestCleanup]
    public void CleanupTest()
    {
        _container.Dispose();
    }

    [TestMethod]
    public void ThingINeedFactory_CreatesExpectedType()
    {
        var factory = _container.Resolve<IThingINeedFactory>();
        var needsThing = new NeedsThing(factory);
        var output = needsThing.PerformSomeFunction(ThingTypes.B);
        Assert.AreEqual(output, typeof(ThingB).Name);
    }

    [TestMethod]
    public void ThingINeedFactory_CreatesDefaultyTpe()
    {
        var factory = _container.Resolve<IThingINeedFactory>();
        var needsThing = new NeedsThing(factory);
        var output = needsThing.PerformSomeFunction(ThingTypes.D);
        Assert.AreEqual(output, typeof(ThingC).Name);
    }
}

可以使用Windsor实现同一工厂,Windsor示例中的工厂可以在Unity中完成。