天天看點

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

ASP.NET

Core在啟動以及後續針對每個請求的處理過程中的各個環節都需要相應的元件提供相應的服務,為了友善對這些元件進行定制,ASP.NET通過定義接口的方式對它們進行了“标準化”,我們将這些标準化的元件稱為服務,ASP.NET在内部專門維護了一個DI容器來提供所需的服務。要了解這個DI容器以及現實其中的服務提供機制,我們先得知道什麼是DI(Dependence

Injection),而一旦我們提到DI,又不得不說IoC(Inverse of Control)。

目錄 一、流程控制的反轉 二、對流程的定制 三、 IoC模式     模闆方法(Template Method)     工廠方法(Factory Method)     抽象工廠(Abstract Factory)

我聽到很多人将IoC說成是一種“面向對象的設計模式”,但在我個人看來IoC不能算作一種“設計模式”,其自身也與“面向對象”沒有直接的關系。很多人之是以不能很準确地了解IoC源于他們忽略了一個最根本的東西,那就是IoC這個短語。換句話說,很多人之是以對IoC産生了諸多誤解是因為他們忽略了IoC的定義。

IoC的全名Inverse of

Control,翻譯成中文就是“控制反轉”或者“控制倒置”。控制反轉也好,控制倒置也罷,它展現的意思是控制權的轉移,即原來控制權在A手中,現在需要B來接管。那麼具體對于軟體設計來說,IoC所謂的控制權的轉移具有怎樣的展現呢?要回答這個問題,就需要先了解IoC的C(Control)究竟指的是怎樣一種控制,在我看來這裡所謂的控制更多地展現為一種“流程的控制”。

我們通過一個具體事例來說明傳統的設計在采用了IoC之後針對流程的控制是如何實作反轉的。比如說我們現在設計一個針對Web的MVC類庫,不妨将其命名為MvcLib。MvcLib提供了如下所示的API幫助我們完成整個HTTP請求流程中的主要任務。具體來說,ListenAndReceiveRequest方法啟動一個監聽器綁定到指定的位址進行請求的監聽,接收到的請求通過一個Request對象傳回。ActivateController方法根據接收到的請求解析并激活請求的目标Controller。ExecuteContrller方法執行激活的Controller并傳回一個表示視圖的View對象。RenderView最終将View對象轉換成HTML并作為目前請求響應的内容。

現在我們在這個MvcLib的基礎上建立一個真正的MVC應用,那麼除了按照MvcLib的規範自定義具體的Controller和View之外,我們還需要自行控制從請求的監聽與接收、Controller的激活與執行以及View的最終呈現在内的整個流程,這樣一個執行流程反映在如下所示的代碼中。

這個例子展現了右如圖所示的流程控制方式,即我們設計的類庫(MvcLib)僅僅通過API的形式提供某種單一功能的實作,作為類庫消費者的應用程式(App)則需要自行編排整個工作流程。如果從重用的角度來講,這裡被重用的僅限于實作某個環節單一功能的代碼,編排整個工作流程的代碼并沒有得到重用。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

當我們建構一個應用的時候,我們不僅僅是需要一個能夠提供API的類庫,實際上更理想的形式是直接在一個現有的架構上構架我們的應用。類庫(Library)和架構(Framework)的不同之處在于,前者往往隻是提供實作某種單一功能的API,而後者則針對一個目标任務對這些單一功能進行編排形成一個完整的流程,這個流程在一個引擎的驅動下被執行。

在我們上面示範MvcLib來說,使用它的應用程式需要自行控制整個HTTP請求的處理流程,實際上這是一個很“泛化”的工作流程,幾乎所有的MVC應用均采用這樣的流程監聽、接收請求并最終對請求予以響應。如果我們将這個流程實作在一個MVC架構之中,由它建構的所有MVC應用就可以直接使用這個請求處理流程,而并不需要自行重複實作它。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式
ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

有了上面示範的這個例子作為鋪墊,我們應該很容易了解IoC所謂的控制反轉了。總的來說,IoC是我們設計架構所采用的設計思想,所謂的控制反轉即是按照如右圖所示的方式将原來實作在應用程式流程控制轉移到架構中。被轉移的是一個泛化的可重用的處理流程,是以IoC符合軟體設計一個基本的原則,即重用性。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

IoC将對流程的控制從應用程式轉移到架構之中,架構利用一個引擎驅動整個流程的執行。換句話說,應用程式無需關心該工作流程的細節,它隻需要啟動這個引擎即可。但是這個引擎一旦被啟動,架構就會完全按照預先編排好的流程進行工作,如果應用程式希望整個流程按照自己希望的方式被執行,針對流程的定制一般在發生在啟動引擎之前。

一般來說,架構會以相應的形式提供一系列的擴充點,應用程式則通過定義擴充的方式實作對流程某個環節的定制。在引擎被啟動之前,應用程式将所需的擴充注冊到架構之中。一旦引擎被正常啟動,這些注冊的擴充會自動參與到整個流程的執行過程中。

雖然應用程式是架構引擎的啟動着,但是一旦引擎被啟動之後它就喪失了對流程的控制,應用程式對流程的定制不是在執行過程中對架構的幹預來完成的,而隻需要在流程執行之前就将定制的部分準備好,架構自身在執行過程中會智能地選擇它們。從這個意義上講,IoC對流程的定制遵循着這樣一個原則,即“Don't call us, we'll call you!”,它被稱為好萊塢原則。

綜上所述,IoC一方面通過流程控制從應用程式向架構的反轉實作了針對流程自身的重用,另一方面采用“好萊塢原則”使得這個被重用的流程可能自由地被定制,這兩個因素實際上決定了架構自身的價值。重用讓架構不僅僅是為應用程式提供實作單一功能的API,而是提供一整套可執行的解決方案;可定制則使我們可以為不同的應用程式對架構進行定制,這無疑讓架構可以使用到更多的應用之中。

正如我們在上面提到過的,很多人将IoC了解為一種“面向對象的設計模式”,實際上IoC自身不僅與面向對象沒有必然的聯系,它也算不上是一種設計模式。一般來講,設計模式提供了一種解決某種具體問題的方案,但是IoC既沒有一個針對性的問題領域,其自身沒有提供一種可實施的解決方案,是以我更加傾向于将IoC視為一種設計原則,實際上很多我們熟悉的設計模式背後采用了IoC原則。

提到IoC,很多人首先想到的是DI,但是在我看來與IoC聯系得最為緊密的則是另一種被稱為“模闆方法”的設計模式。模闆方法模式與IoC的意圖可以說完全一緻,該模式主張将一個可複用的工作流程或者由多個步驟組成的算法定義成模闆方法,組成這個流程或者算法的步驟則實作在相應的虛方法之中,模闆方法根據流程先後調用這些虛方法。所有這些方法均定義在同一個類中,我們可以通過派生該類并重寫相應的虛方法達到對流程定制的目的。

對于上面我們示範的這個MVC的例子,我們可以将整個請求處理流程實作在如下一個MvcEngine類中,請求的監聽與接收、目标Controller的激活與執行以及View的呈現則分别定義在四個受保護的虛方法中,模闆方法Start根據預定義的請求處理流程先後調用這四個方法。

對于具體的應用來說,如果定義在MvcEngine針對請求的處理方式完全符合它的要求,它隻需要建立這個一個MvcEngine對象,然後指定一個對應的基位址調用模闆方法Start開啟這個MVC引擎即可。如果該MVC引擎處理請求的某個環節不能滿足它的要求,它可以建立MvcEngine的派生類,并重寫實作該環節的相應虛方法即可。比如說定義在某個應用程式中的Controller都是無狀态的,它希望采用單例(Singleton)的方式重用已經激活的Controller以提高性能,那麼它就可以按照如下的方式建立一個自定義的FoobarMvcEngine并按照自己的方式重寫OnActivateController方法既可。

模闆方法如果結合“事件注冊”往往可以使應用程式對流程的定制變得更加自由。如下面的代碼片段所示,我們為Controller的激活與執行以及View的呈現定義了六個事件,它們分别在這個三個環節開始之前和結束之後被觸發。這麼一個MvcEngine可以直接被使用,應用程式隻需要注冊相應的事件完成對請求處理流程的定制。

對于一個複雜的流程來說,我們傾向于将組成該流程的各個環節實作在相應獨立的元件之中,那麼針對流程的定制就可以通過提供不同元件的形式來實作。我們知道23種設計模式之中有一種重要的類型,那就是“建立型模式”,比如常用的“工廠方法”和“抽象工廠”,那麼IoC所展現的針對流程的共享與定制可以通過它們來完成。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

同樣以我們的MVC架構為例,我們讓獨立的對象來完成組成整個請求處理流程的四個核心環節。具體來說,我們分别定義了四個核心的類型(Listener、ControllerActivator、ControllerExecutor和ViewGenderer)來分别負責請求監聽與接收、Controller的激活、Controller的執行以及View的呈現。這四個對象按照如右圖所示的順序互相協作完成對請求的處理。

如下所示的Listener、ControllerActivator、ControllerExecutor和ViewGenderer這四個類型的簡單定義。我們沒有将它們定義成單純的抽象類或者接口,而是未被封閉可以被繼承的一般類型,定義其中的虛方法具有預設的實作。隻有這些預設的實作方式無法滿足應用程式具體需求的時候,我們才需要定義相應的派生類。

在作為MVC引擎的MvcEngine類中,我們定義了四個工廠方法(GetListener、GetControllerActivator、GetControllerExecutor和GetViewRenderer)來提供上述這四種類型的對象。這四個工廠方法均為具有預設實作的虛方法,它們預設提供上述四種類型的對象。在用于啟動引擎的Start方法中,我們利用這些工廠方法提供的對象來具體完成請求處理流程的各個核心環節。

對于具體的應用程式來說,如果需要對請求處理的某個環節進行定制,它需要将定制的操作實作在對應類型(Listener、ControllerActivator、ControllerExecutor和ViewGenderer)的派生類中。在MvcEngine的派生類中,我們需要重寫對應的工廠方法來提供被定制的對象。

比如上面提及的以單例模式提供目标Controller對象的實作就定義在SingletonControllerActivator類中,我們在派生于MvcEngine的FoobarMvcEngine類中重寫了工廠方法GetControllerActivator使其傳回一個SingletonControllerActivator對象。

ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式
ASP.NET Core中的依賴注入(1):控制反轉(IoC)一、流程控制的反轉二、對流程的定制三、 IoC模式

具體來說,我們需要定義一個獨立的工廠接口或者抽象工廠類,并在其中定義多個的工廠方法來提供“同一系列”的多個相關對象。如果希望抽象工廠具有一組預設的“産出”,我們也可以将一個未被封閉的具體類作為抽象工廠,以虛方法形式定義的工廠方法将預設的對象作為傳回值。我們根據實際的需要通過實作工廠接口或者繼承抽象工廠類(不一定是抽象類)定義具體工廠類來提供一組定制的系列對象。

現在我們采用抽象工廠模式來改造我們的MVC架構。如下面的代碼片段所示,我們定義了一個執行個體類EngineFactory作為建立MvcEngine所需四種對象(Listener、ControllerActivator、ControllerExecutor和ViewGenderer)的抽象工廠,定義其中的四個工廠方法的傳回值展現了工廠預設的産出。我們在建立MvcEngine對象可以提供一個具體的EngineFactory對象(如果沒有顯式指定,MvcEngine預設使用的是一個自動建立的EngineFactory對象)。在用于啟動引擎的Start方法中,MvcEngine利用EngineFactory來擷取相應的對象協作完整對請求的處理流程。

對于我們采用抽象工廠改造後的MVC架構,以MvcEngine為核心的相關元件之間的關系展現在如左圖所示的UML中。

如果具體的應用程式需要采用上面定義的SingletonControllerActivator以單例的模式來激活目标Controller,我們可以按照如下的方式定義一個具體的工廠類FoobarEngineFactory。最終的應用程式将這麼一個FoobarEngineFactory對象作為MvcEngine的EngineFactory。

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-ioc.html">ASP.NET Core中的依賴注入(1):控制反轉(IoC)</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-di.html">ASP.NET Core中的依賴注入(2):依賴注入(DI)</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-register.html">ASP.NET Core中的依賴注入(3):服務注冊與提取</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-life-time.html">ASP.NET Core中的依賴注入(4):構造函數的選擇與生命周期管理</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-1.html">ASP.NET Core中的依賴注入(5):ServicePrvider實作揭秘【總體設計】</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-2.html">ASP.NET Core中的依賴注入(5):ServicePrvider實作揭秘【解讀ServiceCallSite】</a>

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-service-provider-3.html">ASP.NET Core中的依賴注入(5):ServicePrvider實作揭秘【補充漏掉的細節】</a>

作者:蔣金楠

微信公衆賬号:大内老A

如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識别二維碼)關注個人公衆号(原來公衆帳号蔣金楠的自媒體将會停用)。

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

<a href="http://www.cnblogs.com/artech/p/asp-net-core-di-ioc.html" target="_blank">原文連結</a>

繼續閱讀