pochopeni DI, ruzne zpusoby pristup ke sluzbam
- regiss
- Člen | 61
Nette 2.0betta
Chtel bych uves priklady pro pristup ke sluzbam pres DI. Myslim, ze to pomuze zacatecniku pochopit zaklady DI.
NEON
services:
robotLoader:
run: true
db:
class: DibiConnection
arguments: [%database%]
model:
class: Model
arguments: [@db]
production < common:
development < common:
database:
driver: mysql
host: localhost
username: root
password: xxx
database: xxx
charset: utf8
profiler: true
console < common:
class Model extends NObject
{
/** @var NConnection */
public $database;
public function __construct($database)
{
$this->database = $database;
}
public function test(){
$query = $this->database->select('*')->from('moje-tabulka')->fetchAll();
dump($query);
}
}
class TestPresenter extends BasePresenter
{
public function renderDefault()
{
(1)
Funguje pokud pripojim dibi v bootstrapu, neni potreba registrovat zadnou sluzbu
Do bootstrapu vlozit => dibi::connect( $container->params['database'] );
$test = dibi::select('*')->from('kleankampsiteLink')->fetchAll();
dump($test);
Pristup k modelu pres DI + mozne zpusoby realizace, je nutne zaregistrovat sluzby
Pro sluzby jsou uvedene v neonu.
(2)
$test = new Model($this->getService('db')); //funguje
(3)
$test = $this->getService('model'); //funguje
(4)
$test = $this->context->model; //funguje
(5)
$test = $this->context->db->select('*')->from('moje-tabulka')->fetchAll();//funguje
Zavolani metody v Modelu
$test->test();
Dumpnuti
dump($test);
}
}
Bylo by super kdy nekdo zkusenejsi okomentoval tyto priklady a ujasni jednotlive metody pristupu. Doufam, ze tento prehled nekomu pomuze.
- Tharos
- Člen | 1030
Nepovažuji se za kdo ví jak zkušeného, ale jsi si jist, že konstrukce typu:
$test = $this->getService('model')
$test = $this->context->db->select('*')->from('moje-tabulka')->fetchAll();
$test = new Model($this->getService('db')); // tohle je obzvláště pikantní DI :)
jsou skutečně v duchu Dependency injection ;)? A kdybys dostal za úkol přepsat to v duchu vzoru Service locator, co by se změnilo ;)?
Proč tohle píšu… Cílíš-li na začátečníky, tak tohle je bohužel spíš ještě více zmate. Začátečníky je IMHO nejlepší odkázat na kvalitní literaturu (například na ten mini-seriál na Zdrojáku), kde se dozví, co DI skutečně ve své podstatě je, a poté je jen upozornit, že v Nette je v současné době implementován jakýsi hybrid mezi DI a Service locatorem (který byl v Nette dříve) s řadou dalších specifik (například vytváření více kontejnerů).
Je to tak v Nette beta, ale (naštěstí) se na předělání nyní zdá se aktivně pracuje.
Edit: Abych to ještě trochu doplnil… Tebou uvedené konstrukce jsou úplně validní konstrukce, které se v Nette běžně používají a fungují. Myslím si ale, že je důležité, aby uživatelé těchto konstrukcí věděli, že se nejedná tak úplně o DI přístup, a aby věděli, v čem je ten zádrhel. Pak je vše úplně OK. Však já si taky v Presenterech vesele tahám služby přímo z kontejneru ;)…
Editoval Tharos (27. 10. 2011 14:15)
- bene
- Člen | 82
$test = new Model($this->getService('db'));
Co se ti na tom nezdá?
Třída Model
dostává jinou třídu na které závisí přes
konstruktor, což považuji za „nejčistčí“ DI řešení.
Aha, už jsem asi pochopil, co ti na tom vadí. Že je to v prezenteru?
Pokud máš k dispozici kontejner, je asi hloupost dělat závislost presenteru
na konkrétní třídě Model
.
Editoval bene (28. 10. 2011 13:27)
- Nox
- Člen | 378
Přesně tak
„Kontejner“ v Nette je zavádějící, jelikož to bývá často Container + Factory
A co se týče DI, tam je zásadní pro začátečníky pochopit tu ideu, která je čistě o tom, že se závislosti do kódu (třídě, funkci, skriptu…) předají přímo (konstruktor, setter) a dotyčný kód si ho nevytváří (new), ani nezískává z něčeho globálního (glob. proměnné, statické proměnné) nebo nepřímo ($a = ->…->…->…->b)
Editoval Nox (28. 10. 2011 15:43)
- Tharos
- Člen | 1030
bene napsal(a):
$test = new Model($this->getService('db'));
Co se ti na tom nezdá?
Třída
Model
dostává jinou třídu na které závisí přes konstruktor, což považuji za „nejčistčí“ DI řešení.
Ano, konkrétně to je v duchu DI. Nicméně je v duchu DI jako jediný obrat hned ze tří v tom zápise přítomných. :)
K čemu je to volání $this->getService('db')
? Proč tam
není přímo $this->db
? Služba db
by se pak
pochopitelně v té třídní proměnné ocitla přes konstruktor či
setter.
No a ještě „horší“ je to volání new
. Samozřejmě ne
úplně všechny instance je vhodné vytvářet v kontejneru (určitě si
dokážeš představit, jak produktivní by bylo takto přistupovat třeba
k výjimkám…), ale zrovna modelové třídy jsou v první linii mezi
kandidáty. V duchu DI ten řádek můžeš úplně oželet – Ty
nepotřebuješ vytvářet žádnou instanci třídy Model
a tahat
pro ni odněkud nějakou jinou službu, Ty už to všechno máš připravené.
Tím mám na mysli, že už jsi dostal přes konstruktor či setter hotové
instance všeho potřebného a o jejich původ se vůbec nemusíš starat.
- bene
- Člen | 82
Článek by se měl jmenovat, pochopení Nette „kontejnéru“ v presenteru a přístupu k jeho službám.
Jedinná ukázka co je to vlastně DI je zrovna:
$test = new Model($this->getService('db')); // neřešme teď odkud se bere $this->getService('db')
Jak to tak vypadá, spousta lidí si spojila DI s kontejnérem. Jenže DI je jen metoda vytváření tříd, aby byly znovupoužitelné, testovatelné, bez skrytých závislostí, apod.
Kontejnér je pak pouze sestavovatel těchto tříd. Vůbec neznamená že DI neexistuje bez kontejnéru a kontejnér nemusí vždy sestavovat třídy, které jsou čistě DI.
Odkaz na miniseriál na zdrojáku o DI mohu jen doporučit.
Co se presenteru týče, ten asi v kontruktoru všechny objekty nedostane a proto je předání nějakého kontejneru asi jedinný možný způsob. Mělo by se pak volat jen:
$model = $this->context->getService('Model');
$items = $model->getItems();
Na první pohled to porušuje „Law of Demeter“, ale presenter je zrovna třída, kde bych to nepovažoval za velký problém. Zvlášťe v případě kontejneru. U něj se o tomto porušení v podstatě ani mluvit nedá.
To že Nette umoňuje několik způsobů přístupu považuji za nevhodné. Vede to akorát k různým kódům, které dělají jedno a to samé. Snižuje se pak výhoda frameworku a jednotné práce s ním = problém více lidí na jednom projektu.
Zaznělo tu že Nette „kontejner“ je vlastně Container + Factory. Nenapadlo mě nad tím takto uvažovat, ale asi jo. Jak by jste to teda rozdělily? Předat kontejneru nejaky objekt továrny? a konkrétní metoda pro získání service by nejprve zjistila, jestli už objekt má a když ne, tak by si o něj řekla továrně?
class Container {
private $factory;
private $model;
public function __construct($factory) {
$this->factory = $factory;
}
public function getModel() {
if ($this->model === null) {
$this->model = $this->factory->createModel();
}
return $this->model;
}
}
class Factory {
private $config;
public function __construct($config) {
$this->config= $config;
}
public function createModel() {
return new Model($config['database']);
}
}
Kontejner se v tomto případě stává vlastně jen obálkou, která umožní využívat objekty jako singletony (pokud tak chceme), což je asi čistší než aby to řešila factory.
Tohle je zajimavé téma. Může někdo, kdo s tím má zkušenost to lépe objasnit?
Díky
- Filip Procházka
- Moderator | 4668
Moje vysvětlení dependency injection, ale jak je vidět, tak je to potřeba pořád opakovat dokola :)
Editoval HosipLan (31. 10. 2011 14:38)
- Fanda
- Člen | 39
HosipLan napsal(a):
Moje vysvětlení dependency injection, ale jak je vidět, tak je to potřeba pořád opakovat dokola :)
Výborný vlákno, i Tvoje shrnutí (nejen)…