pochopeni DI, ruzne zpusoby pristup ke sluzbam

Upozornění: Tohle vlákno je hodně staré a informace nemusí být platné pro současné Nette.
regiss
Člen | 61
+
0
-

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
+
0
-

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
+
0
-
$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
+
0
-

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
+
0
-

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
+
0
-

Č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
+
0
-

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
+
0
-

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)…