Dependency injection (předávání závislostí)

Návrhové vzory Dependency injection (předávání závislostí)

Dependency injection (DI) je návrhový vzor pro předávání závislostí mezi jednotlivými komponentami aplikace tak, aby se komponenty mohly vzájemně používat. Díky dependency injection se už nemusíme starat o vytváření závislostí naší třídy.

Motivace

DI není nic složitého. Jde jen o to komponentě závislosti předat namísto toho, aby si je musela sama odněkud získat. Kde závislosti získá, to už je nám jedno. Někdo jiný jí ty závislosti předá. V tom spočívá základní princip Dependency Injection. Proč ale nepoužívat globální či statické metody a vlastnosti, ze kterých si komponenta získá vše potřebné? Jedním z důvodů použití DI je čitelnost. Jasně totiž vidíme, na čem je komponenta závislá. Díky DI můžeme komponentu použít různými způsoby (podle toho, jaké objekty ji předáme), kód je lépe testovatelný a znovupoužitelný. Také nemusíme řešit, odkud závislosti dostaneme. O to se postará ten, kdo danou komponentu bude chtít použít.

Vzor

Jde o pouhé předávání závislostí. Vytváření závislostí a jejich předávání dalším komponentám se děje v dalším objektu (tzv. DI kontejneru), který celý strom závislostí staví a instancuje ještě před tím, než je samotná aplikace spuštěna.

Mějme například galerii obrázků. Budeme potřebovat obrázky odněkud získat například z databáze. K tomu bude zapotřebí získat spojení s databází. Místo toho, abychom hledali ve statickém a globálním prostoru v některém naší třídě neznámém objektu, necháme někoho jiného, ať nám připojení vytvoří a předá. Nemusíme tak řešit, odkud se připojení k databázi vzalo a jak jsme se k databázi připojili, to už vyřešil někdo jiný. Zároveň můžeme snadno objektu „podstrčit“ jiné instance například pro testování. Ukážeme si tedy, jakými způsoby můžeme vyjádřit závislosti naši třídy.

Constructor injection

Constructor injection předpokládá, že jsou všechny objekty potřebné k chodu předané v konstruktoru. Jedná se pravděpodobně o nejčistší řešení, jelikož jsou závislosti na první pohled patrné.

class Gallery
{
        /** @var PDO */
        private $connection;

        public function __construct(PDO $connection)
        {
                $this->connection = $connection;
        }
}
Setter injection

Někdy se může stát, že nebudeme možné, závislosti předat v konstruktoru. Setter injection proto místo konstruktoru používá metody.

class Gallery
{
        /** @var PDO */
        private $connection;

        public function setConnection(PDO $connection)
        {
                $this->connection = $connection;
        }
}
Interface injection

Z názvu již asi tušíte, že se bude jednat o rozhraní. To v sobě bude obsahovat názvy metod, které předají dané objekty.

interface InjectConnection
{
        public function injectConnection(PDO $connection);
}

A takto můžeme rozhraní implementovat.

class Gallery implements InjectConnection
{
        /** @var PDO */
        private $connection;

        public function injectConnection(PDO $connection)
        {
                $this->connection = $connection;
        }
}
Property injection

Spočívá v prostém předávání přímo proměnných objektu.

class Galery
{
        /** @var PDO */
        public $connection;
}

Nejvíc zřejmé jsou závislosti z Constructor injection, jelikož vše musí být v konstruktoru. U property injection není vůbec zřejmé, na čem objekt závisí. Z venku bychom to vůbec nepoznali. To samé platí také pro interface a setter injection. Z toho plyne, že jen nejvíc čitelné je Constructor injection. Doporučuji ho tedy používat. Někdy, pokud máte různé knihovny či frameworky, to ale není možné. Částečně na to ale můžete použít návrhový vzor Adapter. Jinak je výběr vzoru, kterým závislosti předáte, na vás.

Dependency Injection kontejnery

Co když ale komponenta (v našem případě Galerie) získá další závislosti (například Cache)? Bylo by nepraktické měnit vytváření naší třídy všude v aplikaci. Proto můžeme použít továrničku, která nám objekt vytvoří a předá mu závislosti. Ta se označuje jako DI kontejner. Přesněji jde o celý objekt obsahující víc takovýchto továrniček, kde každá vytváří nějakou službu. Služba je zde obyčejný objekt. V souvislosti s DI kontejnery o nich však mluvíme jako o službách. DI kontejner ale není Dependency Injection. Je to jen způsob, jakým lze DI v aplikaci používat.

V DI kontejneru máme plnou kontrolu nad závislostmi všech komponent v aplikaci (nebo alespoň v modulu). Můžeme tak změnit závislosti některé třídy (služby) v systému, aniž bychom museli procházet a měnit všechny třídy, které danou službu používají, což bychom mimochodem bez DI zjišťovali jen velmi těžko.

class Container
{
        private $parameters;

        private $services = array();

        function __construct(array $parameters)
        {
                $this->parameters = $parameters;
        }

        function createConnection()
        {
                return new PDO(
                        "mysql:host={$this->parameters['host']};dbname={$this->parameters['db']}",
                        $this->parameters['user'],
                        $this->parameters['pass'],
                        $this->parameters['options']
                );
        }

        function getConnection()
        {
                if (!isset($this->services['connection']))
                        $this->services['connection'] = $this->createConnection();
                return $this->services['connection'];
        }

        function createGallery()
        {
                return new Gallery($this->getConnection());
        }

}

Kromě továrniček třída obsahuje také metodu getConnection, která si ukládá servisy do privátní proměnné, jelikož například připojení k databázi v tomto případě použijeme jen jedno pro celou aplikaci. Také si všimněte parametrů, které předáváme kontejneru. Galerii pak kontejner předá všechny potřebné objekty v konstruktoru. A takto by mohlo vypadat použití DI kontejneru.

$container = new Container(array(
        "host" => "localhost",
        "db" => "test_db",
        "user" => "root"
        "pass" => "",
        "options" => array(PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8")
));
$container->createGallery();

Disclaimer: příklad použití DI kontejneru je jen ilistrativní a v reálné aplikaci by byl implementován jinak. Kódy jsou v jazyce PHP.

Závěrem

Dependency Injection je v podstatě velmi jednoduchým vzorem, který funguje na principu „ať se postará někdo jiný“. Pokud ho budete správně používat, získáte dobře čitelný a znovupoužitelný kód.


 

  Aktivity (2)

Článek pro vás napsal Drahomír Hanák
Avatar
Autor v současné době studuje Informatiku. Zajímá se o programování, matematiku a grafiku.

Jak se ti líbí článek?
Celkem (11 hlasů) :
4.818184.818184.818184.818184.81818


 


Miniatura
Všechny články v sekci
Návrhové vzory
Miniatura
Následující článek
Servant (Služebník)

 

 

Komentáře

Avatar
zdenek.kral
Člen
Avatar
zdenek.kral:

Preji pekny den,

jsem rad, ze nekdo podobny server o OOP provozuje, ale.....

a)podobnych tutorialu jsou hafa

b)u konkretniho clanku neni uvedeno v jakem jazyce jsou priklady implementovany

  1. pokud jde o orientaci na web, tak je asi vhodne implementovat v PHP, Ruby, Jave ..., pokud jde a desktop aplikace, asi by bylo vhodne upresnit zda jde o Javu, C, C++, python.... ( tvorite pro celosvetovy web, ne pro uzce specializovane programatory..., Ti chodi na jine weby !!!)
  2. UML diagramy lze stahnout kdekoliv, ale uvest aplikaci v konkretnim jazyce je uz o necem jinem ( urcite je neco jineho cashovani front controlleru na klientovi nebo v desktop aplikaci v jinem pojeti memory access or cashe...)!

Takze fandim, ale doporucuji vetsi preciznost ( PS: k obhajeni semestralky na fafce to stacit nebude :))))

S pozdravem Dr.Kral

 
Odpovědět 4.9.2012 20:11
Avatar
Drahomír Hanák
Tým ITnetwork
Avatar
Odpovídá na zdenek.kral
Drahomír Hanák:

Zdravím,
jde spíš o představení, co to DI vůbec je a jak ho implementovat. Za to že jsem neuvedl jazyk se omlouvám. Jde samozřejmě o PHP, ale kromě toho se přeci jedná o návrhový vzor, který lze takto (nebo velmi podobně) implementovat snad v každém objektovém jazyce, a proto jsem také nechtěl uvádět konkrétní aplikaci. Tu když tak mohu uvést samostatně v příslušné sekci.

 
Odpovědět 4.9.2012 22:44
Avatar
Kit
Redaktor
Avatar
Odpovídá na zdenek.kral
Kit:

ad a) to je v pořádku. Alespoň je nějaká pluralita názorů
ad b) z příkladů je patrné, že se jedná o PHP
ad c) úspěšný web netvoří ani tak admini, jako spíš komunita
ad d) uvedený příklad je jistě konkrétnější, než spousta jiných

Články zde píší autoři bez nároku na honorář. Kvalita některých z nich tomu odpovídá - nemám na mysli žádný konkrétní článek, ani tento. Admini však většinu z nich musí ponechat kvůli motivaci.

Odpovědět 4.9.2012 23:11
Vlastnosti objektů by neměly být veřejné. A to ani prostřednictvím getterů/setterů.
Avatar
David Čápka
Tým ITnetwork
Avatar
Odpovídá na zdenek.kral
David Čápka:

Takových článků moc není, alespoň česky a pokud ano, tak jen někdo něco začne, napíše pár článků a už to nedokončí. Na devbooku jich již nejsou jen stovky a je vidět, že takový konec nám jen tak nehrozí.

Tvoříme iterativně, nejprve aby tu něco bylo, potom aby to bylo dokonalé. Když se věci dělají napoprvé dokonale, obvykle se člověk přecení a dopadne to tak, jako většina nám podobných projektů (tedy skončí).

OOP je dosti univerzální, je jedno v čem bude příklad, stejně se ten kód téměř lišit nebude. S UML diagramy předpokládám myslíš ostatní vzory, tedy viz předchozí odstavec.

KIT: Kvalita je v sekcích o které se staráme vysoká, v dalších už je to volnější. Tak je to asi správně.

Odpovědět  +2 4.9.2012 23:18
Miluji svou práci a zdejší komunitu, baví mě se rozvíjet, děkuji každému členovi za to, že zde působí.
Avatar
Ondřej Krsička
Redaktor
Avatar
Ondřej Krsička:

Myslel jsem, že DI je něco hrozně moc profi, ale používal jsem konstruktor DI, ještě když jsem nevěděl co to je :-) Super článek.

 
Odpovědět 12. května 18:09
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 5 zpráv z 5.