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
  • 前言 當別人做大數據用Java、Python的時候,我使用.NET做大數據、數據挖掘,這確實是值得一說的事。 寫的並不全面,但都是實際工作中的內容。 .NET在大數據項目中,可以做什麼? 寫腳本(使用控制台程式+頂級語句) 寫工具(使用Winform) 寫介面、寫服務 使用C#寫代碼的優點是什麼? ...
  • 前言 本文寫給想學C#的朋友,目的是以儘快的速度入門 C#好學嗎? 對於這個問題,我以前的回答是:好學!但仔細想想,不是這麼回事,對於新手來說,C#沒有那麼好學。 反而學Java還要容易一些,學Java Web就行了,就是SpringBoot那一套。 但是C#方向比較多,你是學控制台程式、WebAP ...
  • 某一日晚上上線,測試同學在回歸項目黃金流程時,有一個工單項目介面報JSF序列化錯誤,馬上升級對應的client包版本,編譯部署後錯誤消失。 線上問題是解決了,但是作為程式員要瞭解問題發生的原因和本質。但這都是為什麼呢? ...
  • 本文介紹基於Python語言中TensorFlow的Keras介面,實現深度神經網路回歸的方法。 1 寫在前面 前期一篇文章Python TensorFlow深度學習回歸代碼:DNNRegressor詳細介紹了基於TensorFlow tf.estimator介面的深度學習網路;而在TensorFl ...
  • 前段時間因業務需要完成了一個工作流組件的編碼工作。藉著這個機會跟大家分享一下整個創作過程,希望大家喜歡,組件暫且命名為"easyFlowable"。 接下來的文章我將從什麼是工作流、為什麼要自研這個工作流組件、架構設計三個維度跟大家來做個整體介紹。 ...
  • 1 簡介 我們之前使用了dapr的本地托管模式,但在生產中我們一般使用Kubernetes托管,本文介紹如何在GKE(GCP Kubernetes)安裝dapr。 相關文章: dapr本地托管的服務調用體驗與Java SDK的Spring Boot整合 dapr入門與本地托管模式嘗試 2 安裝GKE ...
  • 摘要:在jvm中有很多的參數可以進行設置,這樣可以讓jvm在各種環境中都能夠高效的運行。絕大部分的參數保持預設即可。 本文分享自華為雲社區《為什麼需要對jvm進行優化,jvm運行參數之標準參數》,作者:共飲一杯無。 我們為什麼要對jvm做優化? 在本地開發環境中我們很少會遇到需要對jvm進行優化的需 ...
  • 背景 我們的業務共使用11台(阿裡雲)伺服器,使用SpringcloudAlibaba構建微服務集群,共計60個微服務,全部註冊在同一個Nacos集群 流量轉發路徑: nginx->spring-gateway->業務微服務 使用的版本如下: spring-boot.version:2.2.5.RE ...
  • 基於php+webuploader的大文件分片上傳,帶進度條,支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況)。文件上傳前先檢測該文件是否已上傳,如果已上傳提示“文件已存在”,如果未上傳則直接上傳。視頻上傳時會根據設定的參數(分片大小、分片數量)進行上傳,上傳過程中會在目標文件夾中生成一個臨 ...
  • 基於php大文件分片上傳至七牛雲,使用的是七牛雲js-sdk V2版本,引入js文件,配置簡單,可以暫停,暫停後支持斷點續傳(刷新、關閉頁面、重新上傳、網路中斷等情況),可以配置分片大小和分片數量,官方文檔https://developer.qiniu.com/kodo/6889/javascrip ...