我在一个具体的例子中对依赖注入实现感到困惑。
假设我们有一个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();
}
}
}
这种方法存在问题:
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");
}
}
}
这种方法存在问题:
还有其他更合适的方法吗?
答案 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中完成。