【问题标题】:Working with Abstract Factory that is injected through DI container使用通过 DI 容器注入的抽象工厂
【发布时间】:2016-07-12 03:04:35
【问题描述】:

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

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

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

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

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

现在我们有了 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 构造函数注入

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

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 反模式)

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

【问题讨论】:

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


    【解决方案1】:

    下面的示例显示了如何使用 Unity 执行此操作。 This blog post 使用 Windsor 解释得更好一些。两者的基本概念完全相同,只是实现方式略有不同。

    我宁愿让我的抽象工厂访问容器。我认为抽象工厂是一种防止依赖容器的方法——我的类只依赖于IFactory,所以它只是使用容器的工厂的实现。 Castle Windsor 更进一步——您为工厂定义接口,但 Windsor 提供实际实现。但这是一个好兆头,相同的方法在这两种情况下都适用,您不必更改工厂接口。

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

    这样做,依赖于IFactory 的类不知道工厂正在使用字符串值来查找正确的类型。在 Windsor 示例中,一个类将 Address 对象传递给工厂,工厂使用该对象根据地址所在的国家/地区确定要使用的地址验证器。除了工厂“知道”如何选择正确的类型之外,没有其他类。这意味着如果您切换到不同的容器,您唯一需要更改的是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>();
        }
    }
    

    这里有一些单元测试。它们表明,在检查了传递给其 Create() 函数的内容后,工厂返回了正确的 IThingINeed 类型。

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

    [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 中完成。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-01-01
      • 1970-01-01
      • 2022-01-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多