Vkládání závislostí
Vkládání závislostí (anglicky Dependency injection (DI)) je v objektově orientovaném programování technika pro vkládání závislostí mezi jednotlivými komponentami programu tak, aby jedna komponenta mohla používat druhou, aniž by na ni měla v době sestavování programu referenci. Dependency injection lze chápat jako novější název pro Inversion of Control (IoC), ale v užším významu.[zdroj?] Jedná se o konkrétní techniku IoC.
Účel
Pokud chce náš objekt používat služby jiného objektu (dále v textu jako komponenta) bez použití DI, pak zodpovídá za celý svůj životní cyklus, tedy inicializaci apod. Při aplikaci DI je ale náš objekt odstíněn od této správy, tu totiž zařizuje poskytovatel závislostí (anglicky dependency provider) neboli kontejner. Náš objekt tedy potřebuje už jen referenci na poskytovatele závislostí, který mu je schopen dodat hned několik různých komponent splňujících očekávání našeho objektu. Každá z těchto komponent může našemu objektu poskytovat rozdílné služby a v tom spočívá síla DI.
DI zahrnuje nejméně tři objekty, které spolu musí spolupracovat. Konzument, tedy objekt požadující služby. Poskytovatele těchto služeb mu dodá DI provider, který ve skutečnosti zodpovídá za celý životní cyklus poskytovatele. Provider neboli injector může být implementován několika způsoby, např. jako lokátor služeb, abstraktní továrna, tovární metoda, nebo pomocí nějakého z řady z frameworků, například Spring.
Martin Fowler poukazuje na tři různé vzory[1], jak lze objektu přidat externí referenci jiného objektu.
- Vkládání rozhraním – externí modul, který je do objektu přidán, implementuje rozhraní, jež objekt očekává v době sestavení programu.
- Vkládání setter metodou – objekt má setter metodu, pomocí níž lze závislost injektovat.
- Injekce konstruktorem – závislost je do objektu injektována v parametru konstruktoru již při jeho zrodu.
Implementace v jazyce Java
Příklad jednoduché injektáže si ukážeme na následujícím příkladě. Představte si člověka (konzumenta), který se chce stát florbalistou. K tomu, aby se jím mohl stát, potřebuje florbalovou hůl (požadovaná závislost). Obyčejný člověk si hůl neumí sám od sebe vyrobit. Programátor mu ji musí dát do ruky. Řekněme, že se hůl vyrábí v továrně na hokejky. Člověk tedy nepotřebuje znát postup, ani umět si hůl vyrobit. Prostě si zajde k výrobci hokejek, nebo si ji dokonce objedná a výrobce mu ji dopraví až do rukou. Výrobce je schopen poskytnout několik odlišných druhů hokejek s rozdílnými vlastnostmi.
Ukázka rozhraní, které budou jednotlivé komponenty implementovat.
public interface IHul{
public void vystrelHoli();
public void nahrejHoli();
}
public interface IClovek{
public void vystrel();
public void nahrej();
}
Těsná závislost bez použití dependency injection
public class Hul implements IHul{
public void vystrelHoli(){
...
}
public void nahrejHoli(){
...
}
}
public class Clovek implements IClovek{
private IHul hul = new Hul();
public void vystrel(){
hul.vystrelHoli();
}
public void nahrej(){
hul.nahrejHoli();
}
}
public class Aplikace{
public static void main(String[] args){
IClovek cl = new Clovek();
cl.vystrel();
cl.nahrej();
}
}
V tomto případě jsou všichni lidé od svého narození svázáni s konkrétní implementací hole. Existuje mezi nimi těsná závislost již v době překladu.
Manuálně vložená (injektovaná) závislost
public class Clovek implements IClovek{
private IHul hul;
public Clovek(IHul hul){
this.hul = hul;
}
public void vystrel(){
hul.vystrelHoli();
}
public void nahrej(){
hul.nahrejHoli();
}
}
public class ClovekFaktory{
public static IClovek vytvorCloveka(){
return new Clovek(new Hul());
}
}
public class Aplikace{
public static void main(String[] args){
IClovek cl = ClovekFaktory.vytvorCloveka();
cl.vystrel();
cl.nahrej();
}
}
Toto řešení sice přesunulo otázku závislosti z Člověka na továrnu, v tomto případě na celou aplikaci, míra závislosti je však stále velká - již při překladu programu musí být známa konkrétní implementace hole.
Závislost řízená frameworkem
Existuje několik různých frameworků, které jsou schopny řídit závislosti mezi komponentami. Vývojář si s jejich pomocí složí svou aplikaci z různých implementací komponent. O injektáž závislostí se v tomto případě stará kontejner, který si závislosti přečte z konfiguračních souborů, ve většině případů se pro tyto účely používá formát XML.
<servis-point id="VytvorFlorbalistu">
<invoke-factory>
<construct class="IClovek">
<servis>Clovek</servis>
<servis>Hul</servis>
</construct>
</invoke-factory>
</servis-point>
public class Aplikace{
public static void main(String[] args){
Service service = (Service)DependencyManager.get("VytvorFlorbalistu");
IClovek cl = (IClovek)service.getServis(IClovek.class);
cl.vystrel();
cl.nahrej();
}
}
Zde již vidíme uvolněnou závislost. Žádná z komponent není v době překladu programu závislá na druhé. Při tvorbě nového florbalisty se využije služba „VytvorFlorbalistu“, kontejner frameworku se podívá do XML souboru, aby zjistil které konkrétní implementace Člověka a Hole má zvolit a sestaví mezi nimi závislost. Implementace lze měnit bez zásahu do kódu aplikace, stačí upravit konfigurační soubor.
Existuje mnoho variant jak přidat objektu závislost na druhý objekt. Záleží na vývojáři, který způsob vyhodnotí jako nejlepší pro svou aplikaci.
Výhody
- Provider může poskytovat řadu komponent k použití bez zásahu do kódu programu.
- O přetypovávání se stará IoC kontejner.
- Závislosti komponent jsou definovány explicitně, tedy jednodušší orientace v závislostech.
- IoC kontejner lze ve většině případů konfigurovat mimo kód, jednotlivé komponenty je možné využít v jiných programech bez přepisování kódu.
Nevýhody
- Menší přehlednost kódu.
- Náročnější správa kódu.
Seznam DI frameworků
Embedded frameworky
- PicoContainer
- Google Guice
Aplikační frameworky
- JBoss Microcontainer
- Spring framework
Standardní
- Java EE EJB 2,3
- Java EE WebBeans(CDI)
Literatura
Prasanna, R.D.2009. Dependency Injection. Manning Publications Co. 2009. ISBN 978-1-933988-55-9.
Reference
- Martin Fowler, http://www.martinfowler.com/articles/injection.html (anglicky)