相依性插入
注意
本電子書於 2017 年春季出版,此後尚未更新。 這本書中有很多仍然有價值的,但一些材料已經過時。
一般而言,具現化物件時會叫用類別建構函式,並將物件需要的任何值作為引數傳遞至建構函式。 這是相依性插入的範例,特別是稱為 建構函式插入。 物件所需的相依性會插入建構函式。
藉由將相依性指定為介面類型,相依性插入可讓具體型別與相依於這些類型的程式代碼分離。 這項技術通常會使用容器來保存介面和抽象類型之間的登錄和對應清單,以及實作或擴充這些類型的具體類型。
也有其他類型的相依性插入,例如 屬性 setter 插入和 方法呼叫插入,但較不常見。 因此,本章只著重於使用相依性插入容器來執行建構函式插入。
相依性插入簡介
相依性插入是控制反轉 (IoC) 模式的特殊版本,而反轉的考量是取得所需相依性的處理序。 使用相依性插入時,另一個類別負責在執行階段將相依性插入物件。 下列程式碼範例示範如何使用相依性插入來建構 ProfileViewModel
類別:
public class ProfileViewModel : ViewModelBase
{
private IOrderService _orderService;
public ProfileViewModel(IOrderService orderService)
{
_orderService = orderService;
}
...
}
建 ProfileViewModel
構函式會 IOrderService
接收實例做為自變數,由另一個類別插入。 類別中唯一的 ProfileViewModel
相依性是在介面類型上。 因此,類別 ProfileViewModel
對負責具現 IOrderService
化對象的類別沒有任何知識。 負責具現化 IOrderService
對象的類別,並將其插入 ProfileViewModel
類別,稱為 相依性插入容器。
相依性插入容器可提供一項功能來具現化類別執行個體,並根據容器組態管理其存留期,以便減少物件間的結合程度。 在物件建立期間,容器會將物件所需的任何相依性插入其中。 若尚未建立這些相依性,容器則會先建立並解析其相依性。
注意
您也可以使用 Factory 手動實作相依性插入。 不過,使用容器可提供額外的功能,例如存留期管理,以及透過元件掃描進行註冊。
使用相依性插入容器有幾項優點:
- 容器會移除類別尋找其相依性及管理其存留期的需求。
- 容器允許對應實作的相依性,而不會影響 類別。
- 容器允許模擬相依性,有助於提升測試性。
- 容器允許應用程式輕鬆新增類別,以增加可維護性。
在使用MVVM的應用程式內容 Xamarin.Forms 中,相依性插入容器通常用於註冊和解析檢視模型,以及註冊服務,並將其插入檢視模型。
有許多可用的相依性插入容器,eShopOnContainers 行動應用程式使用 TinyIoC 來管理應用程式中檢視模型和服務類別的具現化。 相較於大部分已知的容器,TinyIoC 是在評估數個不同的容器之後選擇的,而且相較於大部分已知的容器,在行動平臺上具有優越的效能。 它有助於建置鬆散結合的應用程式,並提供相依性插入容器中常用的所有功能,包括註冊類型對應、解析物件、管理物件存留期的方法,以及將相依物件插入其解析之物件的建構函式。 如需 TinyIoC 的詳細資訊,請參閱 tinyIoC on github.com。
在 TinyIoC 中 TinyIoCContainer
,類型會提供相依性插入容器。 圖 3-1 顯示使用此容器時的相依性,這會具現化 IOrderService
物件並將它 ProfileViewModel
插入類別。
圖 3-1: 使用相依性插入時的相依性
在運行時間,容器必須知道它應該具現化的介面實作 IOrderService
,才能具現化 ProfileViewModel
物件。 這包含:
- 容器,決定如何具現化實作
IOrderService
介面的物件。 這稱為登錄。 - 容器會
IOrderService
具現化實作 介面的物件和ProfileViewModel
物件。 這稱為解析。
最後,應用程式會使用 物件完成, ProfileViewModel
而且會變成可供垃圾收集使用。 此時,如果其他類別不共用相同的實例,垃圾收集行程應該處置 IOrderService
實例。
提示
撰寫與容器無關的程序代碼。 請一律嘗試撰寫與容器無關的程序代碼,以將應用程式與所使用的特定相依性容器分離。
註冊
必須先登錄容器的相依性類型,才能將相依性插入物件。 註冊類型通常牽涉到傳遞容器介面,以及實作介面的具體型別。
在容器中透過程式碼登錄類型和物件有兩種方式:
- 登錄容器的類型或對應。 容器會視需要建置指定類型的執行個體。
- 將容器中的現有物件登錄為單一項目。 容器會視需要傳回現有物件的參考。
提示
相依性插入容器不一定適合。 相依性插入加入的額外的複雜度和需求,對小型應用程式而言可能不適合或實用。 若某類別沒有任何相依性,或不是其他類型的相依性,則放置於容器可能不合理。 此外,若類別具有單一個相依性集合,而這些相依性對該類型不可或缺、且永不變更,則放置於容器可能不合理。
需要相依性插入的類型註冊應該在應用程式中的單一方法中執行,而且應該在應用程式的生命週期早期叫用此方法,以確保應用程式知道其類別之間的相依性。 在 eShopOnContainers 行動應用程式中,這是由 ViewModelLocator
類別執行,它會建 TinyIoCContainer
置 物件,而且是應用程式中唯一保存該對象的參考類別。 下列程式代碼範例示範 eShopOnContainers 行動應用程式如何在 類別中ViewModelLocator
宣告 TinyIoCContainer
物件:
private static TinyIoCContainer _container;
類型會在建構函式中 ViewModelLocator
註冊。 第一次 TinyIoCContainer
建立 實例可達成此目的,如下列程式代碼範例所示:
_container = new TinyIoCContainer();
類型接著會向 TinyIoCContainer
物件註冊,而下列程式代碼範例示範最常見的類型註冊形式:
_container.Register<IRequestProvider, RequestProvider>();
此處顯示的方法會將 Register
介面類型對應至具體類型。 根據預設,每個介面註冊都會設定為單一實例,讓每個相依物件接收相同的共享實例。 因此,只有單 RequestProvider
一實例會存在於容器中,而容器是由需要透過建構函式插入 IRequestProvider
的物件所共用。
具象類型也可以直接註冊,而不需要來自介面類型的對應,如下列程式代碼範例所示:
_container.Register<ProfileViewModel>();
根據預設,每個具體類別註冊都會設定為多重實例,讓每個相依物件都會收到新的實例。 因此,解析 時 ProfileViewModel
,將會建立新的實例,而容器會插入其必要的相依性。
解決方法
當類型登錄後,則可解析或插入為相依性。 當類型正在解析且容器需要建立新的實例時,它會將任何相依性插入實例中。
已解析類型時,通常會發生下列三個狀況之一:
- 若類型尚未登錄,容器會擲回例外狀況。
- 若類型已登錄為單一項目,容器會傳回單一執行個體。 如果這是第一次呼叫類型,容器會視需要建立它,並維護其參考。
- 如果類型尚未註冊為單一實例,容器會傳回新的實例,而且不會維護其參考。
下列程式代碼範例示範如何 RequestProvider
解析先前向 TinyIoC 註冊的類型:
var requestProvider = _container.Resolve<IRequestProvider>();
在此範例中,會要求 TinyIoC 解析型別的具體類型 IRequestProvider
,以及任何相依性。 一般而言, Resolve
當需要特定類型的實例時,會呼叫 方法。 如需控制已解析物件存留期的資訊,請參閱 管理已解析物件的存留期。
下列程式代碼範例示範 eShopOnContainers 行動應用程式如何具現化檢視模型類型和其相依性:
var viewModel = _container.Resolve(viewModelType);
在此範例中,會要求 TinyIoC 解析所要求檢視模型的檢視模型類型,而且容器也會解析任何相依性。 解析類型時,要解析的 ProfileViewModel
相依性是 ISettingsService
對象和 IOrderService
物件。 由於註冊 和類別時SettingsService
使用了介面註冊,TinyIoC 會傳SettingsService
回 和 OrderService
OrderService
類別的單一實例,然後將它們傳遞至 類別的ProfileViewModel
建構函式。 如需 eShopOnContainers 行動應用程式如何建構檢視模型並將其與檢視產生關聯的詳細資訊,請參閱 使用檢視模型定位器自動建立檢視模型。
注意
使用容器來登錄和解析類型具有效能成本,因為容器會使用反映來建立每種類型,特別是在針對應用程式中的每個頁面巡覽重建相依性時。 如果有許多或深度相依性,則建立的成本可能會大幅增加。
管理已解析物件的存留期
使用具體類別註冊類型之後,TinyIoC 的預設行為是每次解析類型時,或相依性機制將實例插入其他類別時,建立已註冊類型的新實例。 在此案例中,容器不會保存已解析對象的參考。 不過,使用介面註冊註冊類型時,TinyIoC 的預設行為是將物件的存留期當做單一對象來管理。 因此,當容器在範圍內時,實例會保留在範圍中,而且會在容器超出範圍且垃圾收集時處置,或當程式碼明確處置容器時處置。
您可以使用 Fluent AsSingleton
和 AsMultiInstance
API 方法覆寫預設 TinyIoC 註冊行為。 例如, AsSingleton
方法可以與 方法搭配 Register
使用,讓容器在呼叫 Resolve
方法時建立或傳回型別的單一實例。 下列程式代碼範例示範如何指示 TinyIoC 建立 類別的單 LoginViewModel
一實例:
_container.Register<LoginViewModel>().AsSingleton();
第一次 LoginViewModel
解析類型時,容器會建立新的 LoginViewModel
物件,並維護其參考。 在的任何後續解析 LoginViewModel
上,容器會傳回先前建立之 LoginViewModel
對象的參考。
注意
在處置容器時,會處置註冊為單一的型別。
摘要
相依性插入可讓您將具體類型與相依於這些類型的程式代碼分離。 這項技術通常會使用容器來保存介面和抽象類型之間的登錄和對應清單,以及實作或擴充這些類型的具體類型。
TinyIoC 是輕量型容器,相較於大部分已知的容器,在行動平臺上具有優越的效能。 它有助於建置鬆散結合的應用程式,並提供相依性插入容器中常用的所有功能,包括註冊類型對應、解析物件、管理物件存留期的方法,以及將相依物件插入其解析之物件的建構函式。