在我接觸IOC和DI 概念的時候是在2016年有幸倒騰Java的時候第一次接觸,當時對這兩個概念很是模糊;后來由于各種原因又回到.net 大本營,又再次接觸了IOC和DI,也算終于搞清楚了IOC和DI 這兩個概念關系,使用過ASP.NET Core的人對這兩個概念一定不陌生,想必很多人還是很難去理解這兩個東西,所以,趁著今天有空,就去把兩個概念捋清楚,并將學習程序的知識點記錄下來,
一、概念
1.1 什么是IOC?
Ioc—Inversion of Control,即控制反轉,其是一種設計思想,而不是一種技術,再沒有使用IOC之前,我們一般是通過new來實體化,從而創建一個物件,但是我們使用IOC之后,創建這個物件的控制權將由內部轉換到外部,那么這個程序便可以理解為控制反轉,也即把物件轉換成抽象物件的依賴.,
同時控制反轉也是一個目標,控制反轉的優點有如下兩點:
- 可以很好的做到
解耦 屏蔽物件的實作細節,只關心動作不關心動作中的細節,
1.2 什么是DI(依賴注入)?
全稱為Dependency Injection,意思自身物件中的內置物件是通過注入的方式進行創建,形象的說,即由容器動態的將某個依賴關系注入到組件之中,
1.3 IOC和DI的聯系?
IOC是一種設計思想,而DI是這種設計思想的一個實作,理解IOC和DI的關鍵是:“誰依賴誰,為什么需要依賴,誰注入誰,注入了什么”,
●誰依賴于誰:當然是應用程式依賴于IoC容器;
●為什么需要依賴:應用程式需要IoC容器來提供物件需要的外部資源;
●誰注入誰:很明顯是IoC容器注入應用程式某個物件,應用程式依賴的物件;
●注入了什么:就是注入某個物件所需要的外部資源(包括物件、資源、常量資料)
1.4 常見的IOC框架,
微軟.net core 內置的DI、Autofac、Unity
以上已經把IOC和DI 這兩個聯系簡要捋清楚了,下面我們一起學習.net core 內置的DI使用,
二、內置IOC
2.1 內置的IOC 有三種生命周期
Transient:瞬時生命周期, Transient服務在每次被請求時都會被創建一個新的物件,這種生命周期比較適用于輕量級的無狀態服務,Scoped: Scoped生命周期的服務是每次web請求被創建,區域單例物件, 在某個區域內是同一個物件(作用域單例,本質是容器單例);一次請求內是一個單例物件,多次請求則多個不同的單例物件.Singleton: Singleton生命能夠周期服務在第一被請求時創建,在后續的每個請求都會使用同一個實體,如果你的應用需要單例服務,推薦的做法是交給服務容器來負責單例的創建和生命周期管理,而不是自己來走這些事情,
我們先來看一張圖:
ASP.NET Core本身已經集成了一個輕量級的IOC容器,開發者只需要定義好介面后(抽象),并且對抽象的介面進行實作,再Startup.cs的ConfigureServices方法里使用對應生命周期的注入,再呼叫的地方進行使用,比如建構式注入等等,
在startup類中ConfigureServices方法對實體進行注冊如下代碼:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
Console.WriteLine("ConfigureServices");
services.AddControllersWithViews();
//注入生命周期為單例的服務
services.AddSingleton<ISingletonService, SingletonService>();
//注入生命周期為Scoped 的服務
services.AddScoped<IScopedService, ScopedService>();
//注入生命周期為瞬時的服務
services.AddTransient<ITransientService, TransientService>();
}
上面代碼我分別注冊了單例、瞬時、作用域的生命周期的服務,
下面簡單寫了一個例子讓大家看看這三個生命周期的實體的代碼
三個生命周期的抽象服務實作代碼如下:
public class ScopedService : IScopedService
{
public string GetInfo()
{
return $"this is scoped service ";
}
}
public class SingletonService : ISingletonService
{
public string GetInfo()
{
return $"this is singleton service";
}
}
public class TransientService : ITransientService
{
public string GetInfo()
{
return $"this is transient service";
}
}
控制器代碼如下:
public IActionResult Index()
{
using (IServiceScope scope = HttpContext.RequestServices.CreateScope())
{
var transientService = scope.ServiceProvider.GetService<ITransientService>();
var transientService2 = scope.ServiceProvider.GetService<ITransientService>();
var result = $"{transientService.GetInfo()} hashCode : {transientService.GetHashCode()} <br/>";
result += $"{transientService2.GetInfo()} hashCode : {transientService2.GetHashCode()} <br/>";
ViewBag.Transient = result;
var scopeService= scope.ServiceProvider.GetService<IScopedService>();
var scopeService2 = scope.ServiceProvider.GetService<IScopedService>();
result = $"{scopeService.GetInfo()} hashCode :{ scopeService.GetHashCode()} <br/>";
result += $"{scopeService2.GetInfo()} hashCode :{ scopeService2.GetHashCode()} <br/>";
ViewBag.Scope = result;
var singletonService = scope.ServiceProvider.GetService<ISingletonService>();
var singletonService2 = scope.ServiceProvider.GetService<ISingletonService>();
result = $"{singletonService.GetInfo()} hashCode:{ singletonService.GetHashCode()} <br/>";
result += $"{singletonService2.GetInfo()} hashCode:{ singletonService2.GetHashCode()} <br/>";
ViewBag.Singletion = result;
}
return View();
}
index.cshtml 視圖代碼如下:
@{
ViewData["Title"] = "Home Page";
}
<b>Transient生命周期</b>
<div>
@Html.Raw(ViewBag.Transient)
</div>
<b>Scoped生命周期</b>
<div>
@Html.Raw(ViewBag.Scope)
</div>
<b>Singletion生命周期</b>
<div>
@Html.Raw(ViewBag.Singletion)
</div>
分別運行兩次的結果如下圖:
從上圖的運行的每個物件的hashCode 的結果看出Transient生命周期是每次獲得物件都是一次新的物件;Scoped生命周期是在作用域是同一個物件,非作用域內則是新的物件;Singletion生命周期是最好理解的,是這個服務啟動后都是一個物件,也即是全域單例物件,
2.2 注入的幾種方式
直接注入IServiceProvider的方式
services.AddSingleton
();
然后在建構式中通過如下方式獲取具體實作
public HomeController(IServiceProvider serviceProvider)
{
var singletonService = serviceProvider.GetService<SingletonService>();
}
通過GetServices方式
services.AddSingleton<ISingletonService, SingletonService>();
然后在建構式中通過如下方式獲取具體實作
public HomeController(IServiceProvider serviceProvider)
{
var singletonService = serviceProvider.GetService<ISingletonService>();
}
建構式直接注入方式(推薦)
public HomeController(ISingletonService singletonService)
{
var _singletonService =singletonService;
}
集合方式注入
這種方式其實就是省去了注入IServiceProvider的程序,直接將GetServices獲取的結果進行注入,首先注入interface及具體實作
services.AddSingleton<ISingletonService, SingletonService1>();
services.AddSingleton<ISingletonService, SingletonService2>();
獲取的方式如下
public HomeController(IEnumerable<ISingletonService> services)
{
var singletoService1 = services.First();
var singletoService2 = services.Skip(1).First();
}
工廠方式注入
然后我們繼續注入Func這個工廠,這里我們按int來回傳不同的實作,當然你也可以采用其他方式比如string
services.AddSingleton(provider =>
{
Func<int, ISingletonService> func = n =>
{
switch (n)
{
case 1:
return provider.GetService<SingletonService1>();
case 2:
return provider.GetService<SingletonService2>();
default:
throw new NotSupportedException();
}
};
return func;
});
然后在建構式中通過如下方式獲取具體實作
public HomeController(Func<int, ISingletonService> funcFactory)
{
var singletonService1 = funcFactory(1);
var singletonService2 = funcFactory(2);
}
除了以上的幾個注入方式外,還可以通過反射的方式批量注入程式集的方式,這里就不一一寫出具體的例子,自己去嘗試,
三、IOC怎么解耦?
學習到這里,大家對IOC和DI 的使用已經有了一定的掌握,上面我提到過IOC的目標是解耦、屏蔽物件的實作細節這兩大優點;再來回顧上面的代碼實作 可以發現,推薦的注入方式是通過抽象介面的方式進行注入而不是直接注入物件方式,
現在我列舉一個企業發展程序中很常見的一個例子,比如:我在一家企業擔任開發作業,開發了一個電商平臺系統,系統中需要用到日志系統,由于當時的各種外在環境,我們使用的日志是nlog 這個日志組件;但是經過平臺的不斷發展后,nlog 日志組件已經不能滿足我們平臺的需求,需要尋求更智能的日志系統,比如Exceptionless,這時候我們就不得不權衡下現有代碼的可維護性,剛好這個電商平臺系統代碼使用了IOC 使得代碼可維護性比較強,日志系統耦合性比較低,只需要簡單的幾行代碼即可實作日志系統的大換血,現在來看下電商系統目前使用的日志系統相關的代碼,
日志組件服務注冊如下代碼:
services.AddSingleton<ILogService, nLogService>();
各業務中使用nlog代碼大概如下:
public HomeController(ILogService LogService)
{
_logService =LogService;
_logService.Info("=========開始訪問========");
}
從上面的代碼中使用日志的相關業務代碼都是通過IOC來進行控制反轉呼叫日志服務,隱藏了日志服務業務的實作細節;使用業務方無需關注日志的實作細節,從而達到 了高度解耦的效果-屏蔽物件實作細節,
現在我們進行日志系統大換血代碼只需要實作一個新的日志服務,我這里創建ExceptionlessLogService類繼承ILogService即可,同時安排對應的人去實作ExceptionlessLogService這個類就可以達到日志系統升級的效果,
更換后的代碼如下:
services.AddSingleton<ILogService, NLogService>();
改成
services.AddSingleton<ILogService, ExceptionlessLogService>();
這樣就達到了一行代碼升級了整個系統的日志系統,業務呼叫方無需任何的改動,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/67539.html
標籤:其他
