Injektáž závislostí
Poznámka:
Tato elektronická kniha byla publikována na jaře roku 2017 a od té doby nebyla aktualizována. Existuje mnoho v knize, která zůstává cenná, ale některé materiály jsou zastaralé.
Konstruktor třídy se obvykle vyvolá při vytváření instance objektu a všechny hodnoty, které objekt potřebuje, jsou předány jako argumenty konstruktoru. Toto je příklad injektáže závislostí a konkrétně se označuje jako injektáž konstruktoru. Závislosti, které objekt potřebuje, se vloží do konstruktoru.
Zadáním závislostí jako typů rozhraní umožňuje injektáž závislostí oddělit konkrétní typy od kódu, který závisí na těchto typech. Obvykle používá kontejner, který obsahuje seznam registrací a mapování mezi rozhraními a abstraktními typy a konkrétní typy, které implementují nebo rozšiřují tyto typy.
Existují také další typy injektáže závislostí, jako je injektáž setter vlastností a injektáž volání metody, ale jsou méně často vidět. Proto se tato kapitola zaměří výhradně na provádění injektáže konstruktoru s kontejnerem injektáže závislostí.
Úvod do injektáže závislostí
Injektáž závislostí je specializovaná verze modelu Inversion of Control (IoC), kde problém invertovaný je proces získání požadované závislosti. Při injektáži závislostí zodpovídá jiná třída za vkládání závislostí do objektu za běhu. Následující příklad kódu ukazuje, jak ProfileViewModel
je třída strukturovaná při použití injektáže závislostí:
public class ProfileViewModel : ViewModelBase
{
private IOrderService _orderService;
public ProfileViewModel(IOrderService orderService)
{
_orderService = orderService;
}
...
}
Konstruktor ProfileViewModel
obdrží IOrderService
instanci jako argument vloženou jinou třídou. Jediná závislost ve ProfileViewModel
třídě je na typu rozhraní. ProfileViewModel
Třída proto nemá žádné znalosti třídy, která je zodpovědná za vytvoření instance objektuIOrderService
. Třída, která je zodpovědná za vytvoření instance objektu IOrderService
a její vložení do ProfileViewModel
třídy, se označuje jako kontejner injektáž závislostí.
Kontejnery injektáže závislostí snižují spojení mezi objekty tím, že poskytují zařízení pro vytvoření instance instancí tříd a správu jejich životnosti na základě konfigurace kontejneru. Během vytváření objektů kontejner vloží všechny závislosti, které objekt vyžaduje. Pokud se tyto závislosti ještě nevytvořily, kontejner nejprve vytvoří a přeloží jejich závislosti.
Poznámka:
Injektáž závislostí lze také implementovat ručně pomocí továren. Použití kontejneru ale poskytuje další možnosti, jako je správa životnosti a registrace prostřednictvím kontroly sestavení.
Použití kontejneru injektáže závislostí má několik výhod:
- Kontejner eliminuje potřebu třídy vyhledat její závislosti a spravovat jejich životnost.
- Kontejner umožňuje mapování implementovaných závislostí bez ovlivnění třídy.
- Kontejner usnadňuje testovatelnost tím, že umožňuje napodobení závislostí.
- Kontejner zvyšuje udržovatelnost tím, že umožňuje snadné přidání nových tříd do aplikace.
V kontextu Xamarin.Forms aplikace, která používá MVVM, se kontejner injektáže závislostí obvykle použije k registraci a překladu modelů zobrazení a k registraci služeb a jejich vkládání do zobrazení modelů.
K dispozici je mnoho kontejnerů injektáže závislostí s mobilní aplikací eShopOnContainers pomocí TinyIoC ke správě vytváření instancí modelů zobrazení a tříd služeb v aplikaci. TinyIoC byl vybrán po vyhodnocení řady různých kontejnerů a má vynikající výkon na mobilních platformách ve srovnání s většinou dobře známých kontejnerů. Usnadňuje vytváření volně propojených aplikací a poskytuje všechny funkce běžně používané v kontejnerech injektáže závislostí, včetně metod registrace mapování typů, řešení objektů, správy životností objektů a vkládání závislých objektů do konstruktorů objektů, které řeší. Další informace o TinyIoC naleznete v tinyIoC na github.com.
V TinyIoC TinyIoCContainer
typ poskytuje kontejner injektáž závislostí. Obrázek 3-1 znázorňuje závislosti při použití tohoto kontejneru, který vytvoří instanci IOrderService
objektu a vloží ho do ProfileViewModel
třídy.
Obrázek 3-1: Závislosti při použití injektáže závislostí
Za běhu musí kontejner vědět, která implementace IOrderService
rozhraní by měla vytvořit instanci, aby mohl vytvořit instanci objektu ProfileViewModel
. Ta zahrnuje tyto kroky:
- Kontejner se rozhoduje, jak vytvořit instanci objektu
IOrderService
, který implementuje rozhraní. To se označuje jako registrace. - Kontejner vytvoří instanci objektu
IOrderService
, který implementuje rozhraní, aProfileViewModel
objekt. To se označuje jako řešení.
Aplikace nakonec dokončí použití objektu ProfileViewModel
a bude k dispozici pro uvolňování paměti. V tomto okamžiku by systém uvolňování paměti měl odstranit IOrderService
instanci, pokud jiné třídy nesdílely stejnou instanci.
Tip
Psaní kódu nezávislého na kontejneru Vždy se pokuste napsat kód nezávislý na kontejneru, který oddělí aplikaci od používaného kontejneru závislostí.
Registrace
Před vložením závislostí do objektu musí být nejprve zaregistrovány typy závislostí v kontejneru. Registrace typu obvykle zahrnuje předání kontejneru rozhraní a konkrétního typu, který implementuje rozhraní.
Existují dva způsoby registrace typů a objektů v kontejneru prostřednictvím kódu:
- Zaregistrujte typ nebo mapování v kontejneru. V případě potřeby kontejner sestaví instanci zadaného typu.
- Zaregistrujte existující objekt v kontejneru jako singleton. V případě potřeby kontejner vrátí odkaz na existující objekt.
Tip
Kontejnery injektáže závislostí nejsou vždy vhodné. Injektáž závislostí přináší další složitost a požadavky, které nemusí být vhodné nebo užitečné pro malé aplikace. Pokud třída nemá žádné závislosti nebo není závislostí pro jiné typy, nemusí mít smysl ji vložit do kontejneru. Kromě toho, pokud třída má jednu sadu závislostí, které jsou integrální pro typ a nikdy se nezmění, nemusí mít smysl jej vložit do kontejneru.
Registrace typů, které vyžadují injektáž závislostí, by se měla provádět v jedné metodě v aplikaci a tato metoda by se měla vyvolat v rané fázi životního cyklu aplikace, aby se zajistilo, že aplikace bude vědět o závislostech mezi jeho třídami. V mobilní aplikaci eShopOnContainers to provádí ViewModelLocator
třída, která sestaví TinyIoCContainer
objekt a je jedinou třídou v aplikaci, která obsahuje odkaz na tento objekt. Následující příklad kódu ukazuje, jak mobilní aplikace eShopOnContainers deklaruje TinyIoCContainer
objekt ve ViewModelLocator
třídě:
private static TinyIoCContainer _container;
Typy jsou registrovány v konstruktoru ViewModelLocator
. Toho dosáhnete tak, že nejprve vytvoříte TinyIoCContainer
instanci, která je ukázaná v následujícím příkladu kódu:
_container = new TinyIoCContainer();
Typy se pak zaregistrují u objektu TinyIoCContainer
a následující příklad kódu ukazuje nejběžnější formu registrace typu:
_container.Register<IRequestProvider, RequestProvider>();
Zde Register
uvedená metoda mapuje typ rozhraní na konkrétní typ. Ve výchozím nastavení se každá registrace rozhraní konfiguruje jako jednoúčelový objekt, aby každý závislý objekt obdržel stejnou sdílenou instanci. Proto v kontejneru bude existovat pouze jedna RequestProvider
instance, která je sdílena objekty, které vyžadují injektáž IRequestProvider
prostřednictvím konstruktoru.
Konkrétní typy lze také zaregistrovat přímo bez mapování z typu rozhraní, jak je znázorněno v následujícím příkladu kódu:
_container.Register<ProfileViewModel>();
Ve výchozím nastavení je každá konkrétní registrace třídy nakonfigurovaná jako více instancí, aby každý závislý objekt obdržel novou instanci. Proto se při ProfileViewModel
vyřešení vytvoří nová instance a kontejner vloží požadované závislosti.
Rozlišení
Jakmile je typ zaregistrovaný, lze ho vyřešit nebo vkládat jako závislost. Při překladu typu a kontejner musí vytvořit novou instanci, vloží do instance všechny závislosti.
Obecně platí, že když se typ vyřeší, stane se jedna ze tří věcí:
- Pokud typ není zaregistrovaný, kontejner vyvolá výjimku.
- Pokud byl typ registrován jako singleton, vrátí kontejner instanci singleton. Pokud se jedná o první volání typu, kontejner ho v případě potřeby vytvoří a udržuje odkaz na něj.
- Pokud typ nebyl registrován jako singleton, kontejner vrátí novou instanci a neudržuje odkaz na něj.
Následující příklad kódu ukazuje, jak lze vyřešit typ, který byl dříve registrován v RequestProvider
TinyIoC:
var requestProvider = _container.Resolve<IRequestProvider>();
V tomto příkladu se TinyIoC zobrazí výzva k vyřešení konkrétního IRequestProvider
typu pro typ spolu se všemi závislostmi. Metoda se obvykle volá, Resolve
pokud je vyžadována instance konkrétního typu. Informace o řízení životnosti vyřešených objektů naleznete v tématu Správa životnosti vyřešených objektů.
Následující příklad kódu ukazuje, jak mobilní aplikace eShopOnContainers vytvoří instanci typů modelů zobrazení a jejich závislostí:
var viewModel = _container.Resolve(viewModelType);
V tomto příkladu se TinyIoC zobrazí výzva k vyřešení typu modelu zobrazení požadovaného modelu zobrazení a kontejner také vyřeší všechny závislosti. Při překladu ProfileViewModel
typu jsou ISettingsService
závislosti, které se mají přeložit, objektem a objektem IOrderService
. Vzhledem k tomu, že se registrace rozhraní použila při registraci SettingsService
a OrderService
tříd, TinyIoC vrátí singleton instance pro třídy SettingsService
a OrderService
pak je předá konstruktoru ProfileViewModel
třídy. Další informace o tom, jak mobilní aplikace eShopOnContainers vytváří modely zobrazení a přidruží je k zobrazením, naleznete v tématu Automatické vytvoření modelu zobrazení pomocí lokátoru modelu zobrazení.
Poznámka:
Registrace a řešení typů v kontejneru má náklady na výkon kvůli použití reflexe kontejneru při vytváření jednotlivých typů, zejména pokud jsou závislosti rekonstruovány pro každou navigaci na stránce v aplikaci. Pokud existuje mnoho nebo hlubokých závislostí, mohou se náklady na vytvoření výrazně zvýšit.
Správa doby života vyřešených objektů
Po registraci typu pomocí konkrétní registrace třídy je výchozím chováním TinyIoC vytvořit novou instanci registrovaného typu při každém vyřešení typu nebo když mechanismus závislostí vloží instance do jiných tříd. V tomto scénáři kontejner neobsahuje odkaz na vyřešený objekt. Při registraci typu pomocí registrace rozhraní je však výchozím chováním TinyIoC spravovat životnost objektu jako singleton. Instance proto zůstává v oboru, zatímco kontejner je v oboru, a je uvolněn, když kontejner zmizí z oboru a je uvolňování paměti, nebo když kód kontejner explicitně vyhazuje.
Výchozí chování registrace TinyIoC je možné přepsat pomocí metod fluent AsSingleton
a AsMultiInstance
API. Například metodu AsSingleton
lze použít s metodou Register
, aby kontejner při volání Resolve
metody vytvořil nebo vrátil jednu instanci typu. Následující příklad kódu ukazuje, jak TinyIoC je instruován k vytvoření singleton instance LoginViewModel
třídy:
_container.Register<LoginViewModel>().AsSingleton();
Při prvním LoginViewModel
vyřešení typu vytvoří kontejner nový LoginViewModel
objekt a zachová na něj odkaz. Při všech následných překladech kontejneru LoginViewModel
vrátí odkaz na LoginViewModel
objekt, který byl dříve vytvořen.
Poznámka:
Typy, které jsou registrovány jako singletony, jsou uvolněny při odstranění kontejneru.
Shrnutí
Injektáž závislostí umožňuje oddělení konkrétních typů od kódu, který závisí na těchto typech. Obvykle používá kontejner, který obsahuje seznam registrací a mapování mezi rozhraními a abstraktními typy a konkrétní typy, které implementují nebo rozšiřují tyto typy.
TinyIoC je jednoduchý kontejner, který nabízí vynikající výkon na mobilních platformách ve srovnání s většinou známých kontejnerů. Usnadňuje vytváření volně propojených aplikací a poskytuje všechny funkce běžně používané v kontejnerech injektáže závislostí, včetně metod registrace mapování typů, řešení objektů, správy životností objektů a vkládání závislých objektů do konstruktorů objektů, které řeší.