依赖倒置原则、IoC容器和依赖注入:第四部分






4.87/5 (27投票s)
为自定义容器实现 LifeTimeOption。
引言
这是我关于依赖倒置原则、IoC容器和依赖注入的文章的第四部分。在上一部分中,我尝试解释了如何构建自己的IoC容器。本部分的文章使用了上一部分中的大部分代码片段。如果您直接访问本部分,可能会发现代码难以理解。因此,请在开始阅读本部分之前,先阅读前面的部分。为了方便导航到本系列中的其他文章,我在下面提供了链接:
- 第一部分:依赖倒置原则
- 第二部分:控制反转和IoC容器
- 第三部分:自定义IoC容器
- 第四部分:带生命周期选项的自定义IoC容器(当前阅读)
- 第五部分:使用Microsoft Unity进行依赖注入(DI)
背景
有时,当我们需要维护对象的状态时,已解析对象的依赖项的生命周期被认为是其中最重要的因素。
我在上一篇文章中提供的实现是最简单的实现,它在每次解析依赖项的新请求时创建一个新对象。
为了测试此场景,让我们按如下方式修改代码文件:
- 每个对象将维护一个名为
CreatedOn
的属性,以便我们可以跟踪对象的创建时间。 - 在不同时间解析依赖项的多个副本,并比较它们的创建时间。
DIP.Abstractions.IReader.cs
public interface IReader
{
DateTime GetCreatedOn();
string Read();
}
DIP.Implementation.KeyboardReader.cs
public class KeyboardReader:IReader
{
private DateTime _createdOn;
public KeyboardReader()
{
_createdOn = DateTime.Now;
}
public DateTime GetCreatedOn()
{
return _createdOn;
}
public string Read()
{
return "Reading from \"Keyboard\"";
}
}
同样,对DIP.Abstractions.IWriter.cs和DIP.Implementation.PrinterWriter.cs进行更改。
现在更改Consumer
的实现以测试结果。
class Program
{
static void Main(string[] args)
{
// Create the container object
Container container = new Container();
// Register all the dependencies
DIRegistration(container);
// Prepare the first copy object and do the copy operation
Copy copy = new Copy(container);
copy.DoCopy();
Console.ReadLine();
// Prepare the second copy object and do the copy operation
Copy copy2 = new Copy(container);
copy2.DoCopy();
Console.ReadLine();
}
static void DIRegistration(Container container)
{
container.Register<IReader,KeyboardReader>();
container.Register<IWriter,PrinterWrter>();
}
}
通过运行应用程序,您将获得与以下类似的输出:
从上面的输出可以看出,Reader
对象的两个实例在不同时间创建。第二个reader
对象的实例与第一个reader
对象的实例不同。这正如我们所期望的那样工作。但有时您可能希望将依赖项解析为同一对象,以便维护状态。
本文的这一部分将解释如何为我们的自定义IoC容器实现LifeTimeOptions
。Microsoft Unity中有不同类型的LifeTimeManager
,具有各种优点。但仅为了理解起见,我将在我们的容器中实现两种LifeTimeOptions
。实现的 कोड 已随本文的这一部分一起提供。欢迎提出任何改进实现的建议。
LifeTimeOption的实现
为了开始实现,让我们创建一个枚举,其中包含我们将要实现的不同的LifeTimeOptions
。
public enum LifeTimeOptions
{
TransientLifeTimeOption,
ContainerControlledLifeTimeOption
}
TransientLifeTimeOption
:此选项告诉容器在每次调用(当有调用解析依赖项时)创建一个新实例。ContainerControlledLifeTimeOption
:此选项告诉容器我们只想维护解析依赖项的一个副本,这样每次调用解析依赖项时都会返回相同的对象。
接下来,我们需要创建一个名为ResolvedTypeWithLifeTimeOptions
的新类型,它将存储有关已解析类型的信息。
public class ResolvedTypeWithLifeTimeOptions
{
public Type ResolvedType { get; set; }
public LifeTimeOptions LifeTimeOption { get; set; }
public object InstanceValue { get; set; }
public ResolvedTypeWithLifeTimeOptions(Type resolvedType)
{
ResolvedType = resolvedType;
LifeTimeOption = LifeTimeOptions.TransientLifeTimeOptions;
InstanceValue = null;
}
public ResolvedTypeWithLifeTimeOptions(Type resolvedType, LifeTimeOptions lifeTimeOption)
{
ResolvedType = resolvedType;
LifeTimeOption = lifeTimeOption;
InstanceValue = null;
}
}
Resolved
Type:依赖项将被解析到的类型。LifeTimeOption
:已创建对象的LifeTime
选项。InstanceValue
:如果LifeTimeOption = ContainerControlledLifeTimeOption
,此属性将生效,并将为所有调用解析依赖项的请求提供相同的对象。
随着新类的引入,我们iocMap
的字典声明将更改为如下:
private Dictionary<Type, ResolvedTypeWithLifeTimeOptions> =
new Dictionary<Type,ResolvedTypeWithLifeTimeOptions>();
接下来,我们需要更新DIP.MyIoCContainer.Container.cs的代码以支持LifeTimeOptions
。
public class Container
{
private Dictionary<Type, ResolvedTypeWithLifeTimeOptions>
iocMap = new Dictionary<Type, ResolvedTypeWithLifeTimeOptions>();
public void Register<T1, T2>()
{
Register<T1, T2>(LifeTimeOptions.TransientLifeTimeOptions);
}
public void Register<T1, T2>(LifeTimeOptions lifeTimeOption)
{
if (iocMap.ContainsKey(typeof(T1)))
{
throw new Exception(string.Format("Type {0} already registered.",
typeof(T1).FullName));
}
ResolvedTypeWithLifeTimeOptions targetType =
new ResolvedTypeWithLifeTimeOptions(typeof(T2),
lifeTimeOption);
iocMap.Add(typeof(T1), targetType);
}
public T Resolve<T>()
{
return (T)Resolve(typeof(T));
}
public object Resolve(Type typeToResolve)
{
// Find the registered type for typeToResolve
if (!iocMap.ContainsKey(typeToResolve))
throw new Exception(string.Format("Can't resolve {0}.
Type is not registered.", typeToResolve.FullName));
ResolvedTypeWithLifeTimeOptions resolvedType = iocMap[typeToResolve];
// Step-1: If LifeTimeOption is ContainerControlled and there is
//already an instance created then return the created instance.
if (resolvedType.LifeTimeOption ==
LifeTimeOptions.ContainerControlledLifeTimeOptions &&
resolvedType.InstanceValue != null)
return resolvedType.InstanceValue;
// Try to construct the object
// Step-2: find the constructor
//(ideally first constructor if multiple constructors present for the type)
ConstructorInfo ctorInfo = resolvedType.ResolvedType.GetConstructors().First();
// Step-3: find the parameters for the constructor and try to resolve those
List<ParameterInfo> paramsInfo = ctorInfo.GetParameters().ToList();
List<object> resolvedParams = new List<object>();
foreach (ParameterInfo param in paramsInfo)
{
Type t = param.ParameterType;
object res = Resolve(t);
resolvedParams.Add(res);
}
// Step-4: using reflection invoke constructor to create the object
object retObject = ctorInfo.Invoke(resolvedParams.ToArray());
resolvedType.InstanceValue = retObject;
return retObject;
}
}
在上面的代码中,我们修改了现有的Register
方法,还有一个重载的Register
方法,它只是更新了已解析类型的LifeTimeOption
属性。
Resolve()
方法也进行了修改,它首先验证依赖项的LifeTimeOption
。如果LifeTimeOption
指定为ContainerControlledLifeTimeOption
,则它会检查该依赖项的对象是否已经创建。如果是,它将返回已创建的对象,否则它会创建一个新对象,存储它,然后将其返回给调用者。
现在,使用LifeTimeOptions.ContainerControlled
选项更新依赖项的注册,并检查结果。
static void DIRegistration(Container container)
{
container.Register<IReader, KeyboardReader>
(LifeTimeOptions.ContainerControlledLifeTimeOption);
container.Register<IWriter, PrinterWriter>
(LifeTimeOptions.TransientLifeTimeOption);
}
请注意,我已经为IReader
依赖项指定了LifeTimeOptions.ContainerControlledLifeTimeOption
,为IWriter
依赖项指定了LifeTimeOptions.TransientLifeTimeOption
。
如下更新Main
函数:
static void Main(string[] args)
{
// Create the container object
Container container = new Container();
// Register all the dependencies
DIRegistration(container);
// Prepare the first copy object and do the copy operation
Copy copy = new Copy(container);
copy.DoCopy();
Console.ReadLine();
// Prepare the second copy object and do the copy operation
Copy copy2 = new Copy(container);
copy2.DoCopy();
Console.ReadLine();
}
让我们尝试运行代码:
现在您可以看到区别。对于IReader
依赖项,我们指定了ContainerControlledLifeTimeOption
。因此,容器只创建一个实例,并在每次调用resolve方法时返回相同的实例。对于IWriter
依赖项,我们指定了TransientLifeTimeOption
。因此,容器在每次调用resolve方法时创建并返回一个新实例。
Microsoft提供了Microsoft Unity用于依赖解析,并具有各种功能。在下一章中,我将解释Microsoft Unity及其功能。
摘要
本文的这一部分解释了为我们的自定义容器实现LifeTimeOption
。撰写本文的目的是为了获得一些关于管理依赖项解析生命周期的知识。Microsoft提供了许多其他功能,我将在本文的下一部分中进行介绍。
如果您有任何建议,请发表您的建议,以使本文更加有效。
历史
- 2020年4月23日:第二次修订(更新了最终文章的链接)
- 2013年4月21日:第一次修订