背景
我們知道在Prism
框架中,框架中最重要的一個組件就是依賴注入框架,這個框架在一定程度上能夠通過一個容器去管理整個框架中所有類的對象及生命周期,並且在引用的時候只需要通過注入接口框架就能夠自動根據接口類型找到特定的實例,這個會省掉大量的創建對象操作,而且在在軟件設計過程中通過IOC容器實現依賴注入能夠最大程度上實現最終的控制反轉,從而保證軟件設計的時候巨大靈活性和擴展性,幾乎在現在所有大型軟件設計中都包含這個思想,所以說項目中使用什么類型的IOC容器能夠決定代碼的設計的效率以及代碼的靈活性,今天通過這篇文章我們來一起了解下在Prism框架中是怎么使用IOC容器的,后面我們重點來分析下這種設計代碼的思想從而為以后的代碼設計提供思路和借鑒,如果對IOC內部實現還有疑惑可以通過我之前寫的一篇MinIoc文章來了解一下IOC容器內部實現的一些關鍵內容。
代碼分析
按照之前解析代碼的思路,我們還是先來看一看在Prism.Core中定義的IOC容器相關的類圖,通過這個類圖首先對框架中的概念有一個大題上面的認知。
1 類圖
通過分析這個類圖我們發現Prism.Core中主要是定義一些基礎的接口,這些接口主要為了定義一種規范,這個其實也很好理解Prism.Core中並不會定義某一種具體的實現,這些IOC接口的實現應該是具體實現中做的事情,因為子類會很方便的進行擴展,特別是現在有很多的IOC框架可以供我們進行選擇,我們知道在Prism 8.x版本以后,Prism框架提供了兩種具體的IOC依賴注入框架,分別是DryIOC和Unity,這個我們會在稍后的部分進行解讀。我們這里先來解析這幾個接口的定義從而對整個體系上面有一個清晰的認知。
2 接口分析
2.1 IContainerRegistry接口
這個接口的作用主要是通過將一系列的類型和對應的實體注入到容器里面,從而方便我們在使用的時候通過類型查找到注入的對象實體,當然這個接口中會定義各種類型的注入方式,這里主體包括幾個部分:1 Register、2 RegisterSingleton、3 RegisterScoped,這幾個用來定義當前定義的注冊對象的生命周期,這個是IOC容器注冊的永恆話題,另外這個接口中還定義了IsRegistered方法用來判斷某個對象是否已經進行過注冊。
我們先來看一下任意一個方法的定義
/// <summary>
/// Registers an instance of a given <see cref="Type"/>
/// </summary>
/// <param name="type">The service <see cref="Type"/> that is being registered</param>
/// <param name="instance">The instance of the service or <see cref="Type" /></param>
/// <returns>The <see cref="IContainerRegistry" /> instance</returns>
IContainerRegistry RegisterInstance(Type type, object instance);
- 接口方法返回值
這里有個關鍵點就是定義的接口返回類型都是
IContainerRegistry
,這樣定義接口方法的返回值有什么好處?其實這樣做的好處就是能夠連續進行類型注冊,這樣能夠讓代碼的結構更加靈活代碼也顯得更加規范。
- RegisterMany方法
對於這個接口其它方法我們都能夠快速理解其意圖但是RegisterMany方法我們不能夠很快的推測其意圖,下面我們通過框架中的單元測試來進行說明,我們先來看看RegisterMany這個方法的單元測試。
[Fact]
public void RegisterWithManyInterfaces()
{
var mock = new Mock<IContainerRegistry>();
mock.Setup(x => x.RegisterMany(It.IsAny<Type>(), It.IsAny<Type[]>()))
.Returns(mock.Object);
var services = new[] { typeof(ITestService), typeof(ITest2Service) };
var cr = mock.Object.RegisterMany<TestService>(services);
Assert.Same(mock.Object, cr);
mock.Verify(x => x.RegisterMany(typeof(TestService), services));
}
我們再來看看這個TestService和ITestService和ITest2Service之間的關系:
private interface ITestService { }
private interface ITest2Service { }
private class TestService : ITestService, ITest2Service { }
也就是說這個接口用於注冊一個對象從多個接口繼承的情況,這個注冊的好處就是后面我們無論從其中哪一個接口作為類型都能夠獲取到當前的TestService對象,這樣就能夠實現最大程度的靈活性。
- RegisterSingle的用法
注冊單例我們也通過幾個單元測試來說明一下,下面通過注冊一個具體類型和通過注冊一個泛型類型來分別進行演示其具體的用法。
[Fact]
public void RegisterFromConcreteType()
{
var mock = new Mock<IContainerRegistry>();
mock.Setup(x => x.Register(typeof(TestService), typeof(TestService)))
.Returns(mock.Object);
var cr = mock.Object.Register(typeof(TestService));
Assert.Same(mock.Object, cr);
mock.Verify(x => x.Register(typeof(TestService), typeof(TestService)));
}
[Fact]
public void RegisterFromGenericConcreteType()
{
var mock = new Mock<IContainerRegistry>();
mock.Setup(x => x.Register(typeof(TestService), typeof(TestService)))
.Returns(mock.Object);
var cr = mock.Object.Register<TestService>();
Assert.Same(mock.Object, cr);
mock.Verify(x => x.Register(typeof(TestService), typeof(TestService)));
}
2.2 IContainerProvider接口
這個接口和IContainerRegistry接口的作用剛好相反,主要是通過剛才具體的類型從具體的容器中找到之前注冊的對象。這個里面我們重點關注的是在這個接口中定義的IScopeProvider對象,具體我們來看下在接口中這個部分的定義。
/// <summary>
/// Creates a new scope
/// </summary>
IScopedProvider CreateScope();
/// <summary>
/// Gets the Current Scope
/// </summary>
IScopedProvider CurrentScope { get; }
這個IScopedProvider接口是如何定義的?
/// <summary>
/// Defines a Container Scope
/// </summary>
public interface IScopedProvider : IContainerProvider, IDisposable
{
/// <summary>
/// Gets or Sets the IsAttached property.
/// </summary>
/// <remarks>
/// Indicates that Prism is tracking the scope
/// </remarks>
bool IsAttached { get; set; }
}
這個接口是從IContainerProvider接口繼承過來的,從類型上面講也是一個IContainerProvider,后面關於這個具體的實現我們可以看看具體的源碼是如何實現CreateScope的,另外這個和前面講的IContainerRegistry中RegisterScoped
方法有什么聯系,后面我們將帶着這些疑問來一一進行解答。
2.3 ContainerLocator
這個是在Prism.Core中定義的一個靜態類,我們來看看這個靜態類有些什么作用?我們先來看看這個類的代碼?
/// <summary>
/// The <see cref="ContainerLocator" /> tracks the current instance of the Container used by your Application
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class ContainerLocator
{
private static Lazy<IContainerExtension> _lazyContainer;
private static IContainerExtension _current;
/// <summary>
/// Gets the current <see cref="IContainerExtension" />.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static IContainerExtension Current =>
_current ?? (_current = _lazyContainer?.Value);
/// <summary>
/// Gets the <see cref="IContainerProvider" />
/// </summary>
public static IContainerProvider Container =>
Current;
/// <summary>
/// Sets the Container Factory to use if the Current <see cref="IContainerProvider" /> is null
/// </summary>
/// <param name="factory"></param>
/// <remarks>
/// NOTE: We want to use Lazy Initialization in case the container is first created
/// prior to Prism initializing which could be the case with Shiny
/// </remarks>
public static void SetContainerExtension(Func<IContainerExtension> factory) =>
_lazyContainer = new Lazy<IContainerExtension>(factory);
/// <summary>
/// Used for Testing to Reset the Current Container
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static void ResetContainer()
{
_current = null;
_lazyContainer = null;
}
}
這個靜態類用於定義一個全局唯一的IContainerExtension對象,到了這里我們還有最后的一個部分就是IContainerExtension對象,我們先來看這個對象的定義。
/// <summary>
/// A strongly typed container extension
/// </summary>
/// <typeparam name="TContainer">The underlying root container</typeparam>
public interface IContainerExtension<TContainer> : IContainerExtension
{
/// <summary>
/// The instance of the wrapped container
/// </summary>
TContainer Instance { get; }
}
/// <summary>
/// A generic abstraction for what Prism expects from a container
/// </summary>
public interface IContainerExtension : IContainerProvider, IContainerRegistry
{
/// <summary>
/// Used to perform any final steps for configuring the extension that may be required by the container.
/// </summary>
void FinalizeExtension();
}
這個接口同時繼承上面的兩個接口,表面繼承的對象同時擁有注冊類型對象和解析類型對象的兩種能力,另外還定義了一個FinalizeExtension方法用於做一些容器終結的操作,除了這個接口以外還定義了一個泛型IContainerExtension
總結
上面的整篇文章就Prism.Core類庫中關於整個IOC的重點內容做了一個分析,在這個部分就整個IOC容器要實現的內容做了一個詳細的接口定義,定義了該如何注冊類型和解析類型,后面的文章中我們會選擇DryIOC容器作為這個容器接口的實現來就每一個部分來進行說明,當然Prism.Core作為一個接口定義在這里已全部分析完畢,並且在Prism.Core這個類庫中的職責已全部完畢后面整個Prism框架內部提供了DryIOC和Unity兩種具體的實現,后面的文章會就這個部分來做一個更加詳細的說明。