ASP.NET Core依賴註入之旅:1.理論概念

来源:https://www.cnblogs.com/green-jcx/archive/2022/07/27/16523627.html
-Advertisement-
Play Games

1.依賴 在理解依賴註入之前,必須先理解其中的依賴是什麼。對於我們開發的程式而言,實際上就是通過不同類型的對象相互協作而構建成的應用,例如在訂單類中,就會引用商品類作為某個屬性。由於類於類之間存在這種引用關係,在類中就避免不了通過“new”對引用的外部類型進行實例化,對於這種現象就會促使應用程式代碼 ...


1.依賴

在理解依賴註入之前,必須先理解其中的依賴是什麼。對於我們開發的程式而言,實際上就是通過不同類型的對象相互協作而構建成的應用,例如在訂單類中,就會引用商品類作為某個屬性。由於類於類之間存在這種引用關係,在類中就避免不了通過“new”對引用的外部類型進行實例化,對於這種現象就會促使應用程式代碼中產生依賴。

對於應用程式代碼中存在的這種“依賴關係”,其實通過一種“機械齒輪圖”就可以很直觀的體會到這種依賴關係所帶來的弊端。每個類就像某個齒輪,齒輪之間的嚙合傳動就像類於類之間的依賴關係。通過“機械齒輪圖”的運作場景,我們不難看出這種結構的一種弊端:就是不同的齒輪會相互影響,如果有一個齒輪出了問題,就可能會影響到整個齒輪組的正常運轉,對於這種現象,放到應用程式中也是無法避免的現實。

當然,這種依賴對於應用程式帶來的後果並不是很直觀,通常在.NET Framework時代可能很多項目都存在這種依賴現象,但這種現象並不是一個良好的設計規範,並且會帶來下麵的問題:

  • 如果依賴的類型需要發生替換,那麼所有引用該類型的類中都必須進行修改,如果引用的位置很多,覆蓋點廣,也就可以印證我們平時所說的“牽一發而動全身”,這種依賴是不利於程式應對需求變化的。
  • 如果依賴的類型本身也依賴其他的類型,就不得為使用某個類型承擔更多開銷。例如A依賴B,B又依賴C、D、E,以此類推可能存在更多的嵌套依賴關係。對於這種情況,A類為了使用B類,則不得不承擔創建B類的依賴項。那麼如果這個A類要發生替換,並且在很多地方被引用,這個修改量會是幾何倍的增長。
  • 依賴項只有一種固定的實現方式,無法Mock很不易於進行單元測試

依賴註入(DI)最終的目的就是解除上面我們所說的依賴,從而實現松耦合的軟體架構體系,並且它延用了控制反轉的思想,擴展為依賴關係的反轉,即將“依賴關係”(具體可以說是創建依賴對象)這件事,從應用程式中轉移到框架之中,這樣一來應用程式就不必通過硬編碼的形式創建對象,而是由框架提供,從而降低與引用的類型的依賴程度。

依賴註入本身和控制反轉一樣,都並不屬於某個編程語言的特定領域,而是屬於一種軟體設計模式,在不同的開發領域有不同的體現形式,例如.NET Core中內置的依賴註入框架、還有Java的Spring、第三方的PicoContainer等等框架。


2.粘合劑

在我們應用程式代碼中通過硬編碼“new”的方式是一種產生依賴的對象創建方式,為瞭解除這種依賴方式,依賴註入框架為我們提供了一個叫做“容器”的概念,我們應用程式的對象將由“容器”為我們創建並提供給我們。

在依賴註入的術語當中,對於容器提供的對象我們統稱為“服務”,服務包括服務類型和服務實例。

由於依賴註入容器是根據服務類型來獲取服務實例的,所以要為某個類型提供服務,則必須先對服務進行註冊,註冊的服務類型我們通常定義為“介面或基類”以此將依賴關係抽象化。註冊時除了服務類型外,還必須指定服務的一個具體實現類,並且註冊的時候還需要指定這個服務的生命周期。

應用程式在完成定義和註冊工作後,對於服務對象的創建和提供則完全交給框架的“容器”來完成。

“容器”在“機械齒輪”的情景模擬中容器就相當於一個第三方的齒輪,起到了一種“粘合劑”的作用,它不直接參与到應用程式的業務功能代碼中,而是由框架負責運行。這樣一來,不僅降低了對象之間的耦合程度,還能為各個類型主動提供所依賴的對象。


3.控制反轉和依賴註入

在很多地方都看到一種說法是:“依賴註入是實現控制反轉的一種方式”。在經過大量的資料查閱之後我們發現,控制反轉主要體現的是一種“任務流程式控制制權”的反轉,而依賴註入則是體現的一種“對象創建權”的反轉,更為抽象的說法應該是“對象依賴關係”的反轉,這表明它們在反轉的“事物”上存在著差異,但是它們反轉的雙方對象都是一致的,即從應用程式中轉移到框架中。

所以基於上訴的分析,我個人認為控制反轉和依賴註入並不是一種等價的概念,所以說“依賴註入是實現控制反轉的一種方式”不是很恰當。這可以從軟體開發教父Martin Fowler說發表文章中的一段話中區分開來,其中這段話翻譯過來大致的含義如下:

當這些容器談論它們如何如此有用,因為它們實現了“控制反轉”時,我最終感到非常困惑。控制反轉是框架的一個共同特征,所以說這些輕量級容器是特殊的,因為它們使用反轉控制,就像說我的車是特殊的,因為它有輪子。

對於這些概念性的技術點,其實通過文字是很難下定義的。這不外乎和我們中國的傳統文化一樣,你問別人何為“道”?“道可道,非常道”,那可能10個人中會有9種解釋。

基於客觀性,我個人根據學習總結,對控制反轉和依賴註入之間的理解是:控制反轉是一個相對於籠統的說法,這就像張三開發的Web應用是面向對象的程式,李四開發的WPF桌面應用也屬於面向對象程式,面向對象只不過是一個程式設計的基本。依賴註入也是將“控制反轉”做為一個框架的基本思想從而設計出來的一套用於實現“依賴關係反轉”的應用框架。

有興趣的朋友可以查閱Martin Fowler發表的一篇關於控制反轉和依賴註入的經典文章進行深入研究:

https://www.martinfowler.com/articles/injection.html#ConcludingThoughts


4.直接依賴和間接依賴

從面向對象編程的角度來講,類型中的欄位或屬性是依賴的一種主要體現形式。如果類型A中具有一個B類型的欄位或屬性,那麼就代表類型A對類型B產生了依賴,這就屬於一種直接的依賴。如果類型B中還存在一個C類型的欄位或屬性,那麼類型A對類型C產生的依賴屬於間接依賴,並且類型C後面間接或直接依賴的類型對於類型A而言都是間接依賴。依賴註入容器在使用的時候,不光對類型A直接依賴的類型進行對象的提供,並且對類型A所有間接依賴的類型也同樣會進行對象提供。

例如下圖包含了Person的直接依賴和間接依賴:

上圖中Computer類對象依賴於Displayer類,所以Displayer類成了Person類的間接依賴。那麼對於依賴註入容器而言,如果要提供Person類的對象,那麼它直接和間接依賴的對象Computer、Displayer都會預先被初始化並自動註入到Person類的對象之中。基於這種註入的特點,我們可以簡單地理解“依賴註入”屬於一種針對依賴欄位或屬性的自動初始化方式。


5.依賴註入的形式

對於每個需要依賴註入容器提供對象的類而言,都需要為依賴註入容器提供註入的形式,也就是告訴容器通過什麼樣的方式將對象傳遞給你,只有提前定義好了註入形式,容器才能對其進行依賴對象的註入。

依賴註入對於設計模式層面而言,其中註入的形式分為三種:1.介面註入、2.設置器註入、3.構造函數註入,對於不同的依賴註入框架而言其中的註入形式也存在差異,本文目前只解釋.NET Core預設支持的構造函數註入的形式進行介紹。

構造函數註入就是:依賴註入容器將依賴的對象作為構造函數的參數傳遞到相應的類中。如下麵的代碼片段所示,Person類中依賴一個IHouse介面類型,而IHouse的實例則通過構造函數中對應類型的參數進行賦值。

1 public class Person
2 {
3     public IHouse House {get;}
4     public Person(IHouse house)
5     {
6         House=house;
7     }
8 }

使用構造器註入方式需要考慮到一個問題,因為構造函數會存在多個,存在多個構造函數情況下,依賴註入容器又會選擇哪一個呢?容器對於這種選擇情況,實際上在不同的依賴註入框架種會存在不同的選擇策略,所以要根據採用的依賴註入框架而定。那麼對於.NET Core而言,它會在所有構造函數的參數列表進行查找,看看哪個構造函數的參數列表是一個“超集”,如果存在“超集”那就會選擇這個“超集”所對應的構造函數,對於這個超集的邏輯後續會詳細展開,這裡只做一個初步的瞭解即可。

1 public class Person
2 {
3     public IHouse House {get;}
4     public ICar Car {get;}
5     
6     public Person(IHouse house)=>House=house;   
7     public Person(IHouse house,ICar car):this(house)=>car=Car; //超集
8 }

6.依賴註入初體驗

依賴註入這個技術知識點僅從理論上也很難直觀的感受到它的“魅力”,接下來我打算通過代碼示例的形式,讓大家體驗感受下依賴註入在ASP.NET Core中簡單的應用形式。

我們根據上面的類圖作為示例的背景,創建一個ASP.NET Core MVC的應用。針對Home控制器中的依賴項computer欄位使用依賴註入的方式創建對象。通常在依賴註入應用場景下,依賴項的類型都定義為介面或基類,所以Home控制器依賴的computer欄位類型定義為了一個介面。該介面有一個實現類為DellComputer,它會在服務註冊時進行使用,DellComputer類的對象會賦值給computer欄位。

接下來我們根據類圖實現具體的編碼步驟:

1.新建ASP.NET Core MVC的項目,並編碼實現相應的類型,由於邏輯比較簡單,所以類型都寫在一個文件中,代碼如下:

 1 namespace DependencyInjectionDemo
 2 {
 3   public interface IComputer
 4     {
 5         string SayHi(); //打招呼
 6     }
 7 
 8     public class Computer : IComputer
 9     {
10         public string SayHi()
11         {
12             return "你好,我是戴爾電腦";
13         }
14     }
15 
16 }

2.在Home控制器中將IComputer介面類型的欄位作為依賴項,並使用構造函數作為依賴註入的方式,然後在控制器的Index方法中使用依賴項中的SayHi方法,將該方法的返回值輸出到視圖。

 1     public class HomeController : Controller
 2     {
 3         private readonly IComputer computer;
 4 
 5         public HomeController(IComputer computer)
 6         {
 7             this.computer = computer;
 8         }
 9 
10         public IActionResult Index()
11         {
12             ViewBag.Msg = this.computer.SayHi();
13             return View();
14         }
15     
16     }

3.在Index視圖中輸出後臺控制器中設置的ViewBag數據,該數據來源於Home控制器的computer欄位,也就是它的依賴項。

 

4.在Startup.cs類的ConfigureServices方法中對Person類所依賴的IComputer服務進行註冊。使用AddSingleton進行服務註冊,第一個泛型參數為服務類型,第二個泛型參數為服務的實現類。該方法還決定了服務對象的生命周期,對於生命周期後面會有專題進行詳細說明,目前無需糾結。此步驟實際上就是告訴容器,我的應用程式需要使用IComputer服務,並且具體的服務實現類是Computer類。

1     public void ConfigureServices(IServiceCollection services)
2         {
3             services.AddSingleton<IComputer, Computer>();
4         }

5.在完成了服務註冊後,對於當前示例的依賴註入運用已經構建完成,我們可以運行項目查看效果。

 

上圖中成功的輸出了Home控制器依賴項(computer欄位)的方法,並且在Home控制器中使用computer欄位並沒有通過“new”的形式對其實例化,就實現了對一個引用類型的方法調用。實際操作體驗後你會發現,在ASP.NET Core中使用依賴註入並沒有什麼難點,但這其中的作用其實都是歸功於依賴註入框架。


 

7.結語

依賴註入通常對於一些初學者來說,它們在實際的項目中都是“無感”的,並且在面對日常的開發工作而言使用起來也很簡單。這種現象本身就是一個優秀框架的設計體現之處,讓一些複雜的東西從應用程式代碼中轉移到框架中,這會讓運用框架的應用程式變得易於開發、易於擴展。

本文內容只能依賴註入做一個基本的介紹,如果想要完全掌握依賴註入框架這隻是一個開頭,後續在ASP.NET Core應用方面還有很多的細節點,例如服務的生命周期、反模式等等。

雖然依賴註入在應用程式中不會涉及很多代碼量,但是它是ASP.NET Core框架的基石,整個ASP.NET Core都建立在一個依賴框架之上。所以掌握好依賴註入是你對ASP.NET Core起碼的誠意,也是.NET開發者基本的素質。

 

知識改變命運
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 俗話話說的號,沒有金剛鑽,也不攬那瓷器活;日誌分析可以說是所有大小系統的標配了,不知道有多少菜鳥程式員有多喜歡日誌,如果沒了日誌,那自己寫的bug想不被別人發現,可就難了; 有了它,就可將bug們統統消化在自己手裡。 當然了,作為一個架構師搭建動手搭建一個日誌平臺也基本是必備技能了,雖然我們說架構師 ...
  • 一、Type介紹 在Python中一切皆對象,類它也是對象,而元類其實就是用來創建類的對象(由於一切皆對象,所以元類其實也是一個對象)。 先來看這幾個例子: 例1: In [1]: type(12) Out[1]: int 通過 type 可以查看對象的類型,也就是查看對象是那一類的,這裡可以看出來 ...
  • 前言 本文基於Dubbo2.6.x版本,中文註釋版源碼已上傳github:xiaoguyu/dubbo 上一篇文章,講了Dubbo的服務導出: Dubbo源碼(三) - 服務導出(生產者) 本文,咱們來聊聊Dubbo的服務引用。 本文案例來自Dubbo官方Demo,路徑為: dubbo/dubbo- ...
  • 大家好,我是二哥呀! 昨天,一位球友問我能不能給他解釋一下 @SpringBootApplication 註解是什麼意思,還有 Spring Boot 的運行原理,於是我就帶著他扒拉了一下這個註解的源碼,還有 SpringApplication 類的 run() 方法的源碼,一下子他就明白了。 你別 ...
  • anonfiles網盤比較特殊,連接的地址經常會變,所以目前市面上的下載器基本都下載不了這上面的大文件,作為一個程式員,必須以身作則,做一個下載器,當然,我也不是某雷之類的這種大公司的下載器引擎的開發者,用Csharp寫的一個簡單的下載器,下載速度比較感人,還看網路條件,我自己下載的時候好的時候單個 ...
  • 一 創建對象時考慮實現比較器 假設有這樣的場景,有一個40個人的學生列表,業務中需針對學生的成績來進行排序。 可以考慮用IComparable介面和ICompare介面實現: class Program { static void Main(string[] args) { var stus = n ...
  • 一:背景 1. 講故事 哈哈,再次見到物流類軟體,上個月有位朋友找到我,說他的程式出現了 CPU 爆高,讓我幫忙看下什麼原因,由於那段時間在苦心研究 C++,分析和經驗分享也就懈怠了,今天就給大家安排上。 話不多說,上 windbg 說話。 二:WinDbg 分析 1. CPU 真的爆高嗎 既然說 ...
  • 分享一個WPF 實現 Windows 軟體快捷小工具 Windows 軟體快捷小工具 作者:WPFDevelopersOrg 原文鏈接:https://github.com/WPFDevelopersOrg/SoftwareHelper 框架使用.NET40; Visual Studio 2019; ...
一周排行
    -Advertisement-
    Play Games
  • 1. 說明 /* Performs operations on System.String instances that contain file or directory path information. These operations are performed in a cross-pla ...
  • 視頻地址:【WebApi+Vue3從0到1搭建《許可權管理系統》系列視頻:搭建JWT系統鑒權-嗶哩嗶哩】 https://b23.tv/R6cOcDO qq群:801913255 一、在appsettings.json中設置鑒權屬性 /*jwt鑒權*/ "JwtSetting": { "Issuer" ...
  • 引言 集成測試可在包含應用支持基礎結構(如資料庫、文件系統和網路)的級別上確保應用組件功能正常。 ASP.NET Core 通過將單元測試框架與測試 Web 主機和記憶體中測試伺服器結合使用來支持集成測試。 簡介 集成測試與單元測試相比,能夠在更廣泛的級別上評估應用的組件,確認多個組件一起工作以生成預 ...
  • 在.NET Emit編程中,我們探討了運算操作指令的重要性和應用。這些指令包括各種數學運算、位操作和比較操作,能夠在動態生成的代碼中實現對數據的處理和操作。通過這些指令,開發人員可以靈活地進行算術運算、邏輯運算和比較操作,從而實現各種複雜的演算法和邏輯......本篇之後,將進入第七部分:實戰項目 ...
  • 前言 多表頭表格是一個常見的業務需求,然而WPF中卻沒有預設實現這個功能,得益於WPF強大的控制項模板設計,我們可以通過修改控制項模板的方式自己實現它。 一、需求分析 下圖為一個典型的統計表格,統計1-12月的數據。 此時我們有一個需求,需要將月份按季度劃分,以便能夠直觀地看到季度統計數據,以下為該需求 ...
  • 如何將 ASP.NET Core MVC 項目的視圖分離到另一個項目 在當下這個年代 SPA 已是主流,人們早已忘記了 MVC 以及 Razor 的故事。但是在某些場景下 SSR 還是有意想不到效果。比如某些靜態頁面,比如追求首屏載入速度的時候。最近在項目中回歸傳統效果還是不錯。 有的時候我們希望將 ...
  • System.AggregateException: 發生一個或多個錯誤。 > Microsoft.WebTools.Shared.Exceptions.WebToolsException: 生成失敗。檢查輸出視窗瞭解更多詳細信息。 內部異常堆棧跟蹤的結尾 > (內部異常 #0) Microsoft ...
  • 引言 在上一章節我們實戰了在Asp.Net Core中的項目實戰,這一章節講解一下如何測試Asp.Net Core的中間件。 TestServer 還記得我們在集成測試中提供的TestServer嗎? TestServer 是由 Microsoft.AspNetCore.TestHost 包提供的。 ...
  • 在發現結果為真的WHEN子句時,CASE表達式的真假值判斷會終止,剩餘的WHEN子句會被忽略: CASE WHEN col_1 IN ('a', 'b') THEN '第一' WHEN col_1 IN ('a') THEN '第二' ELSE '其他' END 註意: 統一各分支返回的數據類型. ...
  • 在C#編程世界中,語法的精妙之處往往體現在那些看似微小卻極具影響力的符號與結構之中。其中,“_ =” 這一組合突然出現還真不知道什麼意思。本文將深入剖析“_ =” 的含義、工作原理及其在實際編程中的廣泛應用,揭示其作為C#語法奇兵的重要角色。 一、下劃線 _:神秘的棄元符號 下劃線 _ 在C#中並非 ...