动手造轮子:实现一个简单的依赖注入(零)
動手造輪子:實(shí)現(xiàn)一個簡單的依賴注入(零)
Intro
依賴注入為我們寫程序帶來了諸多好處,在微軟的 .net core 出來的同時(shí)也發(fā)布了微軟開發(fā)的依賴注入框架 Microsoft.Extensions.DependencyInjection,大改傳統(tǒng) asp.net 的開發(fā)模式,asp.net core 的開發(fā)更加現(xiàn)代化,更加靈活,更加優(yōu)美。
依賴注入介紹
要介紹依賴注入,首先來聊一下控制反轉(zhuǎn)(IoC)
Ioc—Inversion of Control,即“控制反轉(zhuǎn)”,不是什么技術(shù),而是一種設(shè)計(jì)思想。Ioc意味著將你設(shè)計(jì)好的對象交給容器控制,而不是傳統(tǒng)的在你的對象內(nèi)部直接控制。
誰控制誰,控制什么:傳統(tǒng)程序設(shè)計(jì),我們直接在對象內(nèi)部通過 new 進(jìn)行創(chuàng)建對象,是程序主動去創(chuàng)建依賴對象;而IoC是有專門一個容器來創(chuàng)建這些對象,即由 IoC 容器來控制對 象的創(chuàng)建;誰控制誰?當(dāng)然是IoC 容器控制了對象;控制什么?那就是主要控制了外部資源獲取(不只是對象包括比如文件等)。
為何是反轉(zhuǎn),哪些方面反轉(zhuǎn)了:有反轉(zhuǎn)就有正轉(zhuǎn),傳統(tǒng)應(yīng)用程序是由我們自己在對象中主動控制去直接獲取依賴對象,也就是正轉(zhuǎn);而反轉(zhuǎn)則是由容器來幫忙創(chuàng)建及注入依賴對象;為何是反轉(zhuǎn)?因?yàn)橛扇萜鲙臀覀儾檎壹白⑷胍蕾噷ο?#xff0c;對象只是被動的接受依賴對象,所以是反轉(zhuǎn);哪些方面反轉(zhuǎn)了?依賴對象的獲取被反轉(zhuǎn)了。
IoC 對編程帶來的最大改變不是從代碼上,而是從思想上,發(fā)生了“主從換位”的變化。應(yīng)用程序原本是老大,要獲取什么資源都是主動出擊,但是在 IoC/DI 思想中,應(yīng)用程序就變成被動的了,被動的等待 IoC 容器來創(chuàng)建并注入它所需要的資源了。
IoC 很好的體現(xiàn)了面向?qū)ο笤O(shè)計(jì)法則之一—— 好萊塢法則:“別找我們,我們找你”;即由 IoC 容器幫對象找相應(yīng)的依賴對象并注入,而不是由對象主動去找。
DI—Dependency Injection,即“依賴注入”:組件之間依賴關(guān)系由容器在運(yùn)行期決定,形象的說,即由容器動態(tài)的將某個依賴關(guān)系注入到組件之中。依賴注入的目的并非為軟件系統(tǒng)帶來更多功能,而是為了提升組件重用的頻率,并為系統(tǒng)搭建一個靈活、可擴(kuò)展的平臺。通過依賴注入機(jī)制,我們只需要通過簡單的配置,而無需任何代碼就可指定目標(biāo)需要的資源,完成自身的業(yè)務(wù)邏輯,而不需要關(guān)心具體的資源來自何處,由誰實(shí)現(xiàn)。
理解DI的關(guān)鍵是:“誰依賴誰,為什么需要依賴,誰注入誰,注入了什么”,那我們來深入分析一下:
●誰依賴于誰:當(dāng)然是應(yīng)用程序依賴于 IoC 容器;
●為什么需要依賴:應(yīng)用程序需要 IoC 容器來提供對象需要的外部資源;
●誰注入誰:很明顯是 IoC 容器注入應(yīng)用程序里依賴的對象;
●注入了什么:就是注入某個對象所需要的外部資源/依賴。
依賴注入明確描述了 “被注入對象依賴 IoC 容器配置依賴對象”,依賴注入是控制反轉(zhuǎn)設(shè)計(jì)思想的一種實(shí)現(xiàn)。
依賴注入的好處:
對象的創(chuàng)建和銷毀完全交給 ioc 容器去做,不再需要在應(yīng)用中關(guān)心對象的創(chuàng)建的和銷毀,這對于 C# 里的?IDisposable?對象來說尤為重要,自己去 new 的時(shí)候,對于一些新手來說可能會忘記使用?using?或手動?dispose
對象的復(fù)用,有時(shí)候很多對象沒有必要每次用的時(shí)候就去創(chuàng)建一次,使用 ioc 可以控制在同一生命周期內(nèi)的對象只被創(chuàng)建一次
依賴關(guān)系更清晰
更好的實(shí)現(xiàn)面向接口編程,替換實(shí)現(xiàn)只需要注入服務(wù)的時(shí)候換成另外一種實(shí)現(xiàn)就可以了
大概設(shè)計(jì)
大體使用類似于微軟的依賴注入框架,但是比微軟的依賴注入框架簡單一些,性能也有待優(yōu)化。
服務(wù)生命周期:服務(wù)的生命周期沿用微軟的服務(wù)生命周期,分為?Singleton/?Scoped/?Transient,默認(rèn)值是?Singleton?單例模式
服務(wù)注冊方式:支持所有微軟依賴注入的注冊方式,實(shí)例注入/類型注入/接口-實(shí)現(xiàn)注入/func 注入
注入方式:目前僅支持依賴注入,構(gòu)造方法注入,未來暫時(shí)也沒有支持屬性注入的打算(支持的話也不復(fù)雜,但是依賴關(guān)系就不清晰了,也不推薦用),構(gòu)造方法注入支持直接注入?IEnumerable<T>?或?IReadOnlyCollection<T>?或?IReadOnlyList<T>?來支持獲取一個接口多個實(shí)現(xiàn)的注入,支持泛型注入
DI 相關(guān)類圖:
體驗(yàn)一下
可以參考單元測試:
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
源碼已經(jīng)在 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
總結(jié)
以上是生活随笔為你收集整理的动手造轮子:实现一个简单的依赖注入(零)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 聊聊 Docker Swarm 部署 g
- 下一篇: gRPC 流式调用