使用通过 DI 容器注入的抽象工厂
Working with Abstract Factory that is injected through DI container
我对一个具体示例中的依赖注入实现感到困惑。
假设我们有一个 SomeClass class,它具有 IClassX 类型的依赖项。
public class SomeClass
{
public SomeClass(IClassX dependency){...}
}
创建 IClassX 接口的具体实现取决于运行时参数 N。
使用给定的构造函数,我无法配置 DI 容器(使用 Unity),因为我不知道在运行时将使用 IClassX 的什么实现。
Mark Seemann 在他的书 Dependency Injection In .Net 中建议我们应该使用 Abstract Factory 作为注入参数。
现在我们有 SomeAbstractFactory,它 returns 基于运行时参数 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。
但是,我们假设 classes 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 容器将用于初始组合根,然后根据参数 runTimeParam
使用穷人的 DI 到 return 具体的 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 构造函数和 class 实现
- DI 容器不会用于解析依赖项,必须使用可怜人的 DI
2。显式调用 DI 容器
我们不会“更新”ClassX1 或 ClassX2,而是使用 DI 容器来解析它们。
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 反模式)
还有其他更合适的方法吗?
下面的示例显示了如何使用 Unity 执行此操作。 This blog post 使用 Windsor 解释得更好一些。每个的基本概念完全相同,只是实现略有不同。
我宁愿让我的抽象工厂访问容器。我将抽象工厂视为防止依赖容器的一种方式——我的 class 仅依赖于 IFactory
,因此它只是使用容器的工厂的实现。 Castle Windsor 更进一步——您为工厂定义接口,但 Windsor 提供实际的实现。但这是一个好兆头,相同的方法在两种情况下都有效,而且您不必更改工厂接口。
在下面的方法中,需要的是根据工厂的 class 传递一些允许工厂确定要创建哪个实例的参数。工厂会将其转换为字符串,容器会将其与命名实例相匹配。这种方法适用于 Unity 和 Windsor。
这样做 class 取决于 IFactory
并不知道工厂正在使用字符串值来查找正确的类型。在 Windsor 示例中,class 将 Address
对象传递给工厂,工厂使用该对象根据地址的国家/地区来确定使用哪个地址验证器。没有其他 class 但工厂 "knows" 如何选择正确的类型。这意味着如果您切换到不同的容器,您唯一需要更改的是 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()
函数的内容后,他们表明工厂 return 是 IThingINeed
的正确类型。
在这种情况下(可能适用也可能不适用)我还指定了一种类型作为默认值。如果没有向容器注册完全符合要求的内容,那么它可以 return 该默认值。该默认值也可以是没有行为的空实例。但是所有这些选择都在工厂和容器配置中。
[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 中完成。
我对一个具体示例中的依赖注入实现感到困惑。
假设我们有一个 SomeClass class,它具有 IClassX 类型的依赖项。
public class SomeClass
{
public SomeClass(IClassX dependency){...}
}
创建 IClassX 接口的具体实现取决于运行时参数 N。
使用给定的构造函数,我无法配置 DI 容器(使用 Unity),因为我不知道在运行时将使用 IClassX 的什么实现。 Mark Seemann 在他的书 Dependency Injection In .Net 中建议我们应该使用 Abstract Factory 作为注入参数。
现在我们有 SomeAbstractFactory,它 returns 基于运行时参数 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。
但是,我们假设 classes 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 容器将用于初始组合根,然后根据参数 runTimeParam
使用穷人的 DI 到 return 具体的 ClassX1 或 ClassX2public 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 构造函数和 class 实现
- DI 容器不会用于解析依赖项,必须使用可怜人的 DI
2。显式调用 DI 容器
我们不会“更新”ClassX1 或 ClassX2,而是使用 DI 容器来解析它们。
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 反模式)
还有其他更合适的方法吗?
下面的示例显示了如何使用 Unity 执行此操作。 This blog post 使用 Windsor 解释得更好一些。每个的基本概念完全相同,只是实现略有不同。
我宁愿让我的抽象工厂访问容器。我将抽象工厂视为防止依赖容器的一种方式——我的 class 仅依赖于 IFactory
,因此它只是使用容器的工厂的实现。 Castle Windsor 更进一步——您为工厂定义接口,但 Windsor 提供实际的实现。但这是一个好兆头,相同的方法在两种情况下都有效,而且您不必更改工厂接口。
在下面的方法中,需要的是根据工厂的 class 传递一些允许工厂确定要创建哪个实例的参数。工厂会将其转换为字符串,容器会将其与命名实例相匹配。这种方法适用于 Unity 和 Windsor。
这样做 class 取决于 IFactory
并不知道工厂正在使用字符串值来查找正确的类型。在 Windsor 示例中,class 将 Address
对象传递给工厂,工厂使用该对象根据地址的国家/地区来确定使用哪个地址验证器。没有其他 class 但工厂 "knows" 如何选择正确的类型。这意味着如果您切换到不同的容器,您唯一需要更改的是 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()
函数的内容后,他们表明工厂 return 是 IThingINeed
的正确类型。
在这种情况下(可能适用也可能不适用)我还指定了一种类型作为默认值。如果没有向容器注册完全符合要求的内容,那么它可以 return 该默认值。该默认值也可以是没有行为的空实例。但是所有这些选择都在工厂和容器配置中。
[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 中完成。