動手造輪子:實現一個簡單的依賴註入(零) Intro 依賴註入為我們寫程式帶來了諸多好處,在微軟的 .net core 出來的同時也發佈了微軟開發的依賴註入框架 "Microsoft.Extensions.DependencyInjection" ,大改傳統 asp.net 的開發模式,asp.ne ...
動手造輪子:實現一個簡單的依賴註入(零)
Intro
依賴註入為我們寫程式帶來了諸多好處,在微軟的 .net core 出來的同時也發佈了微軟開發的依賴註入框架 Microsoft.Extensions.DependencyInjection,大改傳統 asp.net 的開發模式,asp.net core 的開發更加現代化,更加靈活,更加優美。
依賴註入介紹
要介紹依賴註入,首先來聊一下控制反轉(IoC)
Ioc—Inversion of Control,即“控制反轉”,不是什麼技術,而是一種設計思想。Ioc意味著將你設計好的對象交給容器控制,而不是傳統的在你的對象內部直接控制。
- 誰控制誰,控制什麼:傳統程式設計,我們直接在對象內部通過 new 進行創建對象,是程式主動去創建依賴對象;而IoC是有專門一個容器來創建這些對象,即由 IoC 容器來控制對 象的創建;誰控制誰?當然是IoC 容器控制了對象;控制什麼?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
- 為何是反轉,哪些方面反轉了:有反轉就有正轉,傳統應用程式是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉;而反轉則是由容器來幫忙創建及註入依賴對象;為何是反轉?因為由容器幫我們查找及註入依賴對象,對象只是被動的接受依賴對象,所以是反轉;哪些方面反轉了?依賴對象的獲取被反轉了。
IoC 對編程帶來的最大改變不是從代碼上,而是從思想上,發生了“主從換位”的變化。應用程式原本是老大,要獲取什麼資源都是主動出擊,但是在 IoC/DI 思想中,應用程式就變成被動的了,被動的等待 IoC 容器來創建並註入它所需要的資源了。
IoC 很好的體現了面向對象設計法則之一—— 好萊塢法則:“別找我們,我們找你”;即由 IoC 容器幫對象找相應的依賴對象並註入,而不是由對象主動去找。
DI—Dependency Injection,即“依賴註入”:組件之間依賴關係由容器在運行期決定,形象的說,即由容器動態的將某個依賴關係註入到組件之中。依賴註入的目的並非為軟體系統帶來更多功能,而是為了提升組件重用的頻率,併為系統搭建一個靈活、可擴展的平臺。通過依賴註入機制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標需要的資源,完成自身的業務邏輯,而不需要關心具體的資源來自何處,由誰實現。
理解DI的關鍵是:“誰依賴誰,為什麼需要依賴,誰註入誰,註入了什麼”,那我們來深入分析一下:
●誰依賴於誰:當然是應用程式依賴於 IoC 容器;
●為什麼需要依賴:應用程式需要 IoC 容器來提供對象需要的外部資源;
●誰註入誰:很明顯是 IoC 容器註入應用程式里依賴的對象;
●註入了什麼:就是註入某個對象所需要的外部資源/依賴。
依賴註入明確描述了 “被註入對象依賴 IoC 容器配置依賴對象”,依賴註入是控制反轉設計思想的一種實現。
依賴註入的好處:
- 對象的創建和銷毀完全交給 ioc 容器去做,不再需要在應用中關心對象的創建的和銷毀,這對於 C# 里的
IDisposable
對象來說尤為重要,自己去 new 的時候,對於一些新手來說可能會忘記使用using
或手動dispose
- 對象的復用,有時候很多對象沒有必要每次用的時候就去創建一次,使用 ioc 可以控制在同一生命周期內的對象只被創建一次
- 依賴關係更清晰
- 更好的實現面向介面編程,替換實現只需要註入服務的時候換成另外一種實現就可以了
大概設計
大體使用類似於微軟的依賴註入框架,但是比微軟的依賴註入框架簡單一些,性能也有待優化。
- 服務生命周期:服務的生命周期沿用微軟的服務生命周期,分為
Singleton
/Scoped
/Transient
,預設值是Singleton
單例模式 - 服務註冊方式:支持所有微軟依賴註入的註冊方式,實例註入/類型註入/介面-實現註入/func 註入
- 註入方式:目前僅支持依賴註入,構造方法註入,未來暫時也沒有支持屬性註入的打算(支持的話也不複雜,但是依賴關係就不清晰了,也不推薦用),構造方法註入支持直接註入
IEnumerable<T>
或IReadOnlyCollection<T>
或IReadOnlyList<T>
來支持獲取一個介面多個實現的註入,支持泛型註入
DI 相關類圖:
體驗一下
可以參考單元測試:
using(IServiceConatiner container = new ServiceContainer())
{
container.AddSingleton<IConfiguration>(new ConfigurationBuilder()
.AddJsonFile("appsettings.json")
.Build()
);
container.AddScoped<IFly, MonkeyKing>();
container.AddScoped<IFly, Superman>();
container.AddScoped<HasDependencyTest>();
container.AddScoped<HasDependencyTest1>();
container.AddScoped<HasDependencyTest2>();
container.AddScoped<HasDependencyTest3>();
container.AddScoped(typeof(HasDependencyTest4<>));
container.AddTransient<WuKong>();
container.AddScoped<WuJing>(serviceProvider => new WuJing());
container.AddSingleton(typeof(GenericServiceTest<>));
var rootConfig = container.ResolveService<IConfiguration>();
Assert.Throws<InvalidOperationException>(() => container.ResolveService<IFly>());
Assert.Throws<InvalidOperationException>(() => container.ResolveRequiredService<IDependencyResolver>());
using (var scope = container.CreateScope())
{
var config = scope.ResolveService<IConfiguration>();
Assert.Equal(rootConfig, config);
var fly1 = scope.ResolveRequiredService<IFly>();
var fly2 = scope.ResolveRequiredService<IFly>();
Assert.Equal(fly1, fly2);
var wukong1 = scope.ResolveRequiredService<WuKong>();
var wukong2 = scope.ResolveRequiredService<WuKong>();
Assert.NotEqual(wukong1, wukong2);
var wuJing1 = scope.ResolveRequiredService<WuJing>();
var wuJing2 = scope.ResolveRequiredService<WuJing>();
Assert.Equal(wuJing1, wuJing2);
var s0 = scope.ResolveRequiredService<HasDependencyTest>();
s0.Test();
Assert.Equal(s0._fly, fly1);
var s1 = scope.ResolveRequiredService<HasDependencyTest1>();
s1.Test();
var s2 = scope.ResolveRequiredService<HasDependencyTest2>();
s2.Test();
var s3 = scope.ResolveRequiredService<HasDependencyTest3>();
s3.Test();
var s4 = scope.ResolveRequiredService<HasDependencyTest4<string>>();
s4.Test();
using (var innerScope = scope.CreateScope())
{
var config2 = innerScope.ResolveRequiredService<IConfiguration>();
Assert.True(rootConfig == config2);
var fly3 = innerScope.ResolveRequiredService<IFly>();
fly3.Fly();
Assert.NotEqual(fly1, fly3);
}
var flySvcs = scope.ResolveServices<IFly>();
foreach (var f in flySvcs)
f.Fly();
}
var genericService1 = container.ResolveRequiredService<GenericServiceTest<int>>();
genericService1.Test();
var genericService2 = container.ResolveRequiredService<GenericServiceTest<string>>();
genericService2.Test();
}
更多詳情可以參考:< https://github.com/WeihanLi/WeihanLi.Common/blob/dev/test/WeihanLi.Common.Test/DependencyInjectionTest.cs >
More
源碼已經在 Github 上,可以自行下載閱覽或等後面的幾篇文章分享解讀
Reference
- https://blog.csdn.net/sinat_21843047/article/details/80297951
- https://www.cnblogs.com/artech/p/inside-asp-net-core-03-04.html
- https://github.com/aspnet/DependencyInjection/tree/rel/2.0.0
- https://github.com/microsoft/MinIoC
- https://github.com/WeihanLi/WeihanLi.Common/tree/dev/src/WeihanLi.Common/DependencyInjection