控制反轉、依賴注入(IOC、DI)

  • IOC:  Inversion Of Control 控制反轉
  • DI:   Dependency  Injection 依賴注入

 

 

 1.控制反轉 Inversion Of Control 的前世此生

1.1  IOC理論產生的背景

討論控制反轉以前,先看看軟件系統提出控制反轉的前世此生。
一個完整精密的軟件系統,組件之間就像齒輪,協同工做,相互耦合。web

  • 一個零件不正常,整個系統就崩潰了。
  • 系統對象之間耦合關係沒法避免,在項目規模和複雜度變大的狀況下,管理類之間的依賴關係將會很複雜。
  • 對象之間耦合度很高的系統,架構師和開發人員對於系統的修改,必然會出現牽一髮而動全身的情形。
  • 對象之間耦合性依賴,單元測試很複雜。
1.2 IOC理論

軟件專家爲此提出IOC理論,用來實現對象之間的解耦。
再來看看,控制反轉(IOC)到底爲何要起這麼個名字?咱們來對比一下:面試

  • 軟件系統在沒有引入IOC容器以前,對象A依賴於對象B,那麼對象A在初始化或者運行到某一點的時候,本身必須主動去建立對象B或者使用已經建立的對象B。不管是建立仍是使用對象B,控制權都在本身手上。
  • 軟件系統在引入IOC容器以後,這種情形就徹底改變了,因爲IOC容器的加入,對象A與對象B之間失去了直接聯繫,因此,當對象A運行到須要對象B的時候,IOC容器會主動建立一個對象B注入到對象A須要的地方。

 

 

 經過先後對比,咱們不難看出:
對象A得到依賴對象B的過程,由主動變爲了被動行爲,控制權顛倒過來,這就是「控制反轉」的由來。sql

1.3 控制反轉 和 依賴注入

有些人會把控制反轉和依賴注入等同,實際上有本質區別:
控制反轉是一種思想;依賴注入是一種設計模式。
依賴注入是實現控制反轉的一種方式,可是控制反轉還有其餘實現方式,例如說ServiceLocator(服務定位器、依賴查找),因此不能將控制反轉和依賴注入等同。設計模式

 

 

 2 依賴注入 Dependency  Injection

依賴注入:容器全權負責組件的裝配,它會把符合依賴關係的對象經過屬性或者構造函數傳遞給須要的對象。架構

符合依賴倒置原則,高層模塊不該該依賴低層模塊,二者都應該依賴其抽象app

2.1 ASP.NET Core依賴注入

使用方式大致相似:框架

  • 定義依賴實現的接口或者抽象類
  • 在服務容器中註冊組件依賴 :IServiceProvider
  • 在構造函數中注入服務, 框架會負責建立和銷燬實例
 1 // 編寫組件和服務
 2 public interface IMyDependency
 3 {
 4     string WriteMessage(string message);
 5 }
 6 ---
 7 public class MyDependency : IMyDependency
 8 {
 9     public string WriteMessage(string message)
10     {
11        return $"MyDependency.WriteMessage Message: {message}";
12     }
13 }
14 // 註冊組件和依賴,下面註冊的`IMyDependency`在一個web請求中有效
15 public void ConfigureServices(IServiceCollection services)
16 {
17     services.AddScoped<IMyDependency, MyDependency>();
18     services.AddRazorPages();
19 }
20 ---
21 // 在構造函數注入組件
22 public class HomeController: AbpController
23 {
24     private readonly IMyDependency _dep;
25     public HomeController(IMyDependency dep)
26     {
27        _dep = dep;
28     }
29 
30   public IActionResult Index()
31   {
32     var content =  _dep.WriteMessage($"The Reflection instance is {_dep.GetType().FullName} ");
33     return Content(content);
34   }
35 }

在請求某個服務時,框架會完整解析出這個對象的依賴樹和做用範圍。webapp

上面的示例代碼造成 req--->HomeController--->IMyDependency依賴樹。編輯器

 IMyDependency在每一個web請求範圍內使用同一服務實例。ide

輸出:MyDependency.WriteMessage Message: The Reflection instance is TestDI.MyDependency

2.2 對象生命週期

根據現實須要,前人從使用場景中總結出三種服務生命週期。
ASP.NET Core提供了一個枚舉ServiceLifetime

-- --- --- ---
Singleton 單例 服務容器首次請求會建立,後續都使用同一實例 AddSingleton
Scoped 特定範圍 在一個請求(鏈接)週期內使用一個示例 AddScoped
Transient 瞬時 服務容器每次請求,都會建立一個實例 AddTransient

對於Scoped Service的理解:

 

 

在webapp:scoped service 會在請求結束時被銷燬;
在EFCore:使用AddDbContext默認註冊的是特定範圍的DbContext,這意味在咱們能夠在一次sql鏈接內,使用同一個DbContext實例進行屢次DB操做。

2.3 依賴注入實現原理

結合理論、使用方式 猜想依賴注入的原理:
實現DI,核心在於依賴注入容器IContainer,該容器具備如下功能
①.(容器)保存可用服務的集合
//  要用的特定對象、特定類、接口服務

②.(註冊)提供一種方式將各類部件與他們依賴的服務綁定到一塊兒;
//  Add...函數或containerBuilder.Register函數

③.(解析點)爲應用程序提供一種方式來請求已配置的對象:構造函數注入、屬性注入.

運行時,框架會一層層經過反射構造實例,最終獲得完整對象。

3.源碼導航

利用反射產生對象是依賴注入的核心過程,這也是面試造航母時常常問到的。

.NETSystem.ReflectionSystem.Type命名空間中的類能夠獲取可裝配組件、類、接口的信息,並提供了在運行時建立實例,調用動態實例方法、獲取動態實例的能力。

實際上,咱們能夠在依賴樹的尾部對象的構造函數手動拋出異常,異常的調用棧就是一個自然的源碼導航。

因而我在上面示例代碼的request----> HomeController--->MyDependency MyDependency構造函數中添加異常代碼:

1    public MyDependency()
2     {
3        throw new Exception("exception content!");
4     }

結果以下圖:

 

從Github Dependency Injection 庫進入System.Reflection的調用分界線代碼:

 1 protected override object VisitConstructor(ConstructorCallSite constructorCallSite, RuntimeResolverContext context)
 2 {
 3     object[] parameterValues;
 4     if (constructorCallSite.ParameterCallSites.Length == 0)
 5     {
 6          parameterValues = Array.Empty<object>();
 7     }
 8     else
 9     {
10        parameterValues = new object[constructorCallSite.ParameterCallSites.Length];
11        for (var index = 0; index < parameterValues.Length; index++)
12       {
13          parameterValues[index] = VisitCallSite(constructorCallSite.ParameterCallSites[index], context);
14        }
15     }
16 
17     try
18     {
19        return constructorCallSite.ConstructorInfo.Invoke(parameterValues);
20     }
21     catch (Exception ex) when (ex.InnerException != null)
22     {
23        ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
24       // The above line will always throw, but the compiler requires we throw explicitly.
25       throw;
26      }
27 }

黃色背景行就是.NET反射特性的體現:
對類型信息(構造函數、參數)使用Invoke方法產生對象。

乾貨旁白

  • 控制反轉是一種在軟件工程中解耦合的思想,調用方依賴接口或抽象類,減小了耦合,控制權交給了服務容器,由容器維護註冊項,並將具體的實現動態注入到調用方。
  • 有些人會把控制反轉和依賴注入等同,實際上有本質區別:
  • 控制反轉是一種思想;
  • 依賴注入是一種設計模式。
  • 依賴注入是實現控制反轉的一種方式,可是控制反轉還有其餘實現方式,例如說ServiceLocator,因此不能將控制反轉和依賴注入等同。
  • 在運行時,框架會解析依賴樹、依賴圖,經過反射在運行期生成對象。
相關文章
相關標籤/搜索