目錄
介紹
這是我有關依賴反轉原理,IoC容器和依賴注入的文章的第四部分。在本文的上半部分,我試圖解釋如何構建自己的IoC容器。本文的這一部分使用了上一部分中的大多數代碼片段。如果您是該部分的直接訪問者,可能會很難理解代碼。因此,在開始閱讀本文之前,請仔細閱讀前面的部分。爲了輕鬆瀏覽該系列的其他文章,我提供了以下鏈接:
- 第1部分:依賴倒置原則
- 第2部分:控制反轉和IoC容器
- 第3部分:自定義IoC容器
- 第4部分:具有生命週期選項的自定義IoC容器(當前正在閱讀)
- 第5部分:使用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 類型:將解決依賴關係的類型
- 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提供了其他一些功能,我將在本文的下一部分中進行解釋。