.net程序開發(fā)IOC控制反轉(zhuǎn)和DI依賴注入詳解
目錄
- IOC控制反轉(zhuǎn)
- DI依賴注入
- 服務(wù)生命周期
- 其它
IOC控制反轉(zhuǎn)
大部分應(yīng)用程序都是這樣編寫的:編譯時依賴關(guān)系順著運行時執(zhí)行的方向流動,從而生成一個直接依賴項關(guān)系圖。 也就是說,如果類 A 調(diào)用類 B 的方法,類 B 調(diào)用 C 類的方法,則在編譯時,類 A 將取決于類 B,而 B 類又取決于類 C
應(yīng)用程序中的依賴關(guān)系方向應(yīng)該是抽象的方向,而不是實現(xiàn)詳細(xì)信息的方向。而這就是控制反轉(zhuǎn)的思想。
應(yīng)用依賴關(guān)系反轉(zhuǎn)原則后,A 可以調(diào)用 B 實現(xiàn)的抽象上的方法,讓 A 可以在運行時調(diào)用 B,而 B 又在編譯時依賴于 A 控制的接口(因此,典型的編譯時依賴項發(fā)生反轉(zhuǎn))。 運行時,程序執(zhí)行的流程保持不變,但接口引入意味著可以輕松插入這些接口的不同實現(xiàn)。
上下不同的實現(xiàn)方式在于之前的依賴關(guān)系是A->B->C,控制反轉(zhuǎn)后A->B接口->C接口,然后具體的B,C實現(xiàn)又是B->B接口 的反轉(zhuǎn)依賴。這樣的好處就是A只依賴B接口而不是依賴實現(xiàn),具體我們要實現(xiàn)什么只需要按照業(yè)務(wù)需求進(jìn)行編寫,并且可以隨時替換實現(xiàn)而不會影響A的實現(xiàn),這種思想就是控制反轉(zhuǎn)。
如下是順序依賴:
public class A{ //依賴具體類 public B b; public C c; public A(B _b, C _c) {b = _b;c = _c; } public void Listen() {b.SayHi();c.SayBye(); }}public class B{ public void SayHi() {Console.WriteLine("hi..."); }}public class C{ public void SayBye() {Console.WriteLine("bye..."); }}
如下是控制反轉(zhuǎn):
public class A{ //依賴接口 public IB b; public IC c; public A(IB _b, IC _c) {b = _b;c = _c; } public void Listen() {b.SayHi();c.SayBye(); }}public interface IB{ public void SayHi();}public interface IC{ public void SayBye();}
DI依賴注入
.NET 支持依賴關(guān)系注入 (DI) 軟件設(shè)計模式,這是一種在類及其依賴項之間實現(xiàn)控制反轉(zhuǎn) (IoC) 的技術(shù)。
我們首先用代碼來看什么是DI,在.net提供的擴(kuò)展包Microsoft.Extensions.DependencyInjection中來完成DI,nuget安裝。
然后我們實現(xiàn)接口B和接口C,實現(xiàn)我們可以說英語,也可以說漢語,我們在SayHi和SayBye中輸出漢語。
public class B : IB{ public void SayHi() {Console.WriteLine("你好..."); }}public class C : IC{ public void SayBye() {Console.WriteLine("再見..."); }}
然后在服務(wù)容器中注冊依賴關(guān)系。 .NET 提供了一個內(nèi)置的服務(wù)容器 IServiceProvider。 服務(wù)通常在應(yīng)用啟動時注冊,并追加到 IServiceCollection。 添加所有服務(wù)后,可以使用 BuildServiceProvider 創(chuàng)建服務(wù)容器,然后在容器中直接“要”對象而不用去管它如何實例化,并且DI具備傳染性
,假如B引用了D接口ID,那么我們注冊B并在獲取B實例時,引用的D接口也會被實例化。
//IServiceCollection 服務(wù) IServiceCollection services = new ServiceCollection(); //服務(wù)注冊 services.AddTransient<A>(); services.AddTransient<IB, B>(); services.AddTransient<IC, C>(); //創(chuàng)建服務(wù)容器 var serviceProvider = services.BuildServiceProvider(); //獲取服務(wù) var a = serviceProvider.GetRequiredService<A>(); //使用 a.Listen(); Console.ReadKey();
這就是通過DI依賴注入的方式來實現(xiàn)IOC的思想,或許你會好奇為什么我們不直接實例化A,然后在構(gòu)造方法里面?zhèn)鬟M(jìn)去就行了,也就不依賴DI實現(xiàn)了。但是程序結(jié)構(gòu)更復(fù)雜些呢,比如上面提到的B又有D,D又有F呢,這樣在構(gòu)造的時候不是一直要new很多對象,而且同一個接口的不同實現(xiàn)還要去找實例化處的代碼進(jìn)行修改。例如SayHI我想說英文呢?那么我們就可以實現(xiàn)一個BB,然后在服務(wù)注冊的地方注冊BB就可以了。
public class BB : IB{ public void SayHi() {Console.WriteLine("hello..."); }}
替換注冊BB services.AddTransient<IB, BB>()
,而不用去改任何邏輯。
服務(wù)生命周期
在注冊服務(wù)的時候我使用的AddTransient
方法,表示注冊的服務(wù)是瞬態(tài)的,也就是每次請求都是重新創(chuàng)建實例。同時還提供其它注冊服務(wù)的方法。
服務(wù)有三種聲明周期:
瞬態(tài)
作用域
單例
- 瞬態(tài)
服務(wù)是每次從服務(wù)容器進(jìn)行請求時創(chuàng)建的。 這種生存期適合輕量級、 無狀態(tài)的服務(wù)。 用 AddTransient 注冊服務(wù)。在處理請求的應(yīng)用中,在請求結(jié)束時會釋放暫時服務(wù)。
- 作用域
指定了作用域的生存期指明了每個客戶端請求(連接)創(chuàng)建一次服務(wù)。 向 AddScoped 注冊范圍內(nèi)服務(wù)。在處理請求的應(yīng)用中,在請求結(jié)束時會釋放有作用域的服務(wù)。
想asp.net 在處理一個請求的時候是一個作用域,同樣我們自己也可以定義作用域。使用serviceProvider.CreateScope()
創(chuàng)建作用域,在作用域釋放后對象將被釋放。
我們使用AddScoped添加對象,然后在作用域中取兩個A對象進(jìn)行比較,可以看到是True
。
如果我們用AddTransient注冊A,即使在作用域內(nèi)兩個對象比較也是不一樣的,結(jié)果為False
。
- 單例
單例大家應(yīng)該好理解,就是設(shè)計模式中的單例,使用AddSingleton 注冊,在首次請求它們時進(jìn)行創(chuàng)建;或者在向容器直接提供實現(xiàn)實例時由開發(fā)人員進(jìn)行創(chuàng)建。 很少用到此方法,因為可能是線程不安全的,如果服務(wù)中有狀態(tài)。
其它
在Microsoft.Extensions.DependencyInjection中只能用構(gòu)造函數(shù)注入,其它框架還提供屬性注入,比如autofac。至于原因不得而知,當(dāng)然也看個人喜好。查了些資料說是構(gòu)造函數(shù)注入更科學(xué),在對象創(chuàng)建的瞬間對象的構(gòu)造方法將服務(wù)實例化,避免邏輯問題。
以上就是.net程序開發(fā)IOC控制反轉(zhuǎn)和DI依賴注入詳解的詳細(xì)內(nèi)容,更多關(guān)于.net 控制反轉(zhuǎn)依賴注入的資料請關(guān)注其它相關(guān)文章!
