Vianoce v ITnetwork sú tu! Dobí si teraz kredity a získaj až 80 % extra kreditov na e-learningové kurzy ZADARMO. Zisti viac.
Hľadáme nové posily do ITnetwork tímu. Pozri sa na voľné pozície a pridaj sa k najagilnejšej firme na trhu - Viac informácií.

5. diel - Zlé spôsoby odovzdávania závislostí - Singleton a SL

V minulej lekcii, Zlé spôsoby odovzdávania závislostí - Statika , sme si predstavili niekoľko spôsobov, ktorými sa dá vo viacvrstvových aplikáciách vysporiadať so závislosťami. Už vieme, že akonáhle je celá aplikácia napísaná v jednom súbore, je minimálne neprehľadná, pri dnešnej zložitosti softvéru ak nie už nevytvořitelná. Akonáhle aplikáciu rozdelíme na vrstvy a vrstvy na objekty, nutne začneme riešiť otázku komunikácie medzi týmito objektmi. Keď objekt komunikuje s iným, hovoríme, že má závislosti.

V dnešnom tutoriále o návrhu softvéru sa budeme venovať so svojimi vzoru Singleton, ktorý súvisí so statikou. Tú sme prebrali minule. Ďalej si ukážeme vzor service locator.

Chyba č. 3 - Závislosti odovzdávame Singleton

Singleton, česky jedináčik, je pomerne kontroverzné návrhový vzor z populárnej skupiny vzorov GOF. Hoci ho uvidíte tu na príklade, môžete si o ňom prečítať tunajšie detailné článok pre prípad, že by ste chceli ďalšie informácie.

Singleton je principiálne podobný nášmu statickému obale z minulej lekcie. Je dôležité spomenúť, že Singleton používa statiku, ukladá inštanciu závislosti do statického atribútu a ponúka taktiež statickú metódu na jej získanie. Platí pre neho teda úplne všetky negatívne vlastnosti, ako sme si spomenuli u statiky. Naviac je pravý Singleton napr. V PHP pomerne zložité vytvoriť. Že je návrhovým vzorom ho nerobí v ničom lepšom a minimálne pre odovzdávanie závislostí je to anti-pattern. Podľa môjho názoru je jeho použitie menej vhodné, než len čisté použitie statiky. Ale to je skôr vec osobného vkusu. Singleton možno napísať thread-safe a môže nájsť svoje uplatnenie pri práci s vláknami, kde jeho využitie dáva zmysel. Na závislosti sa však nehodí.

Ukážme si, ako by vyzeralo odovzdávanie pripojenej inštancie PDO (databázy) našim modelom v aplikácii pre evidenciu automobilov.

Modely / Databaze.php

Pre získanie databázové inštancie si implementujeme Singleton:

class Databaze
{
    private static $pdo;

    private function __construct() { /* Prázdné */ }

    public static function vratInstanci()
    {
        if (!self::$pdo)
            self::$pdo = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
        return self::$pdo;
    }

    final public function __clone()
    {
        throw new Exception('Klonování není povoleno');
    }

    final public function __wakeup()
    {
        throw new Exception('Deserializace není povolena');
    }

}

V triede vidíme na prvý pohľad opäť statický atribút s inštanciou PDO ako u Wrapper z minulej lekcie. Neobalujeme tu však jednotlivé metódy PDO, ale iba poskytujeme jednu metódu pre vrátenie celej inštancie. Všimnite si lazy-loading, teda, že sa inštancia vytvorí až keď si o ňu povieme a potom sa vždy vráti táto jedna inštancia. Ako atribút s PDO, tak metóda vratInstanci() sú statické, aby sme ich mohli používať odkiaľkoľvek. Čo ale keď niekto vytvorí novú databázu zavolaním new Databaze() ? Vytvárať inštanciu Singleton nemá zmysel, pretože slúži len na získanie obsahu statického atribútu. Preto tvorbu inštancií triedy Databaze zakážeme a to vytvorením privátneho konstruktoru. Keďže v PHP je možné tvoriť inštancia ešte príkazom clone alebo deserializací, musíme zakázať aj tieto akcie. Vidíte, že Singleton začína byť trochu magický.

Modely / SpravceAut.php

V modeli si jednoducho povieme o inštanciu pomocou statickej metódy a uložíme si ju:

class SpravceAut
{
    private $databaze;

    public function __construct()
    {
        $this->database = Databaze::vratInstanci();
    }

    public function vratAuta()
    {
        return $this->databaze->query("SELECT * FROM auta")->fetchAll(PDO::FETCH_ASSOC);
    }

}

Už bolo povedané, že máme stále všetky nevýhody statiky. A kód je oproti príkladu sa statickým Wrapper ešte dlhší, ten vyzeral takto:

class SpravceAut
{

    public function vratAuta()
    {
        return Databaze::dotazVsechny("SELECT * FROM auta");
    }

}

Singleton nám teda naozaj moc nepomáha.

Kontrolery / AutaKontroler.php

Kontrolér bude identický, správca vytvoríme nové a nemusíme mu nič odovzdávať, pretože si databázu získa sám:

class AutaKontroler
{
    public function vsechna()
    {
        $spravceAut = new SpravceAut();
        $auta = $spravceAut->vratAuta(); // Proměnná pro šablonu
        require('Sablony/auta.phtml'); // Načtení šablony
    }
}

Problémy prístupu

Zostávajú všetky problémy spojené so statikou:

  • Databáza je prístupná odkiaľkoľvek, teda z kontroleru, ale i napr. Z šablóny! Z ktoréhokoľvek PHP súboru, ktorý ju ani nepotrebuje. To nie je práve bezpečné a zvádza to k zlému návrhu a porušovania Low Coupling.
  • Databáza sa bude veľmi zle Mocková (nahradiť testovacími dátami), čo znepríjemní testovanie, až sa aplikácia stane väčšou
  • Aplikácia "klame" o svojich závislostiach, nie je na prvý pohľad zrejmé aké triedy ktorá trieda používa. Aby sme to zistili, musíme prejsť celý zdrojový kód a všímať si statických prístupov.
  • Veľkým problémom môže byť používanie statiky vo viacvláknových aplikáciách, v PHP sa do tejto situácie pravdepodobne nedostanete, ale v iných jazykoch vstúpite do race condition. Singleton by teda musel byť navyše napísaný ako thread-safe, čo ten náš nie je.

Máme tu ďalšie problémy:

  • Oproti statickému Wrapper musíme inštanciu niekam ukladať, kód je zbytočne dlhší
  • Musíme zabezpečiť, aby mohla existovať vždy len jedna inštancia

A jednu výhodu:

  • Nemusíme wrappovat všetky metódy z PDO

Singleton sa na odovzdávaní závislosťou jasne nehodí.

Chyba č. 4 - Závislosti združujeme do service lokátora

Možno by vás napadlo vytvoriť si jeden kontajner so všetkými závislosťami celej aplikácie a odovzdávať všade tento jeden kontajner. Objekty si potom z kontajnera vyťaháme čo potrebujú. Tento spôsob sa používa napr. V hernom frameworku MonoGame pre C# .NET alebo v jazyku Ruby.

Vytvoríme si teda triedu reprezentujúci kontajner, tam si uložíme inštanciu našej databázy a potom budeme všade manuálne odovzdávať tento kontajner. Pokiaľ bude závislostí veľa, stále odovzdávame iba jednu na kontajner. To by malo byť lepšie, nie? No ... Poďme to skúsiť:

Modely / ServiceLocator.php

Pripravme si kontajner so zdieľanými službami:

class ServiceLocator
{
    private $databaze;

    private __construct()
    {
        // Může být i líně
        $this->databaze = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
    }

    public function vratDb()
    {
        return $this->$databaze;
    }

}

Index.php

Niekde na začiatku aplikácie si kontajner instanciujeme a následne ho musíme všade manuálne odovzdávať:

// Autoloader
// ...

$locator = new ServiceLocator(); // Vytvoří lokátor a závislosti v něm

// Kód pro vytvoření kontroleru...
$kontroler = new $nazevKontroleru($locator); // Předání lokátoru kontroleru

// ...

Kód je podobný tomu z index.php z lekcie o dvojvrstvovej architektúre. Tam sme takto odovzdávali databázu, my tu odovzdávame lokátor, v ktorom môže byť závislosťou viac.

Kontrolery / AutaKontroler.php

V kontroleru si kontajner opäť uložíme a budeme ho odovzdávať všetkým modelom, v našom prípade do SpravceAut:

class AutaKontroler
{
    private $locator;

    public function __construct(ServiceLocator $locator)
    {
        $this->locator = $locator;
    }

    public function vsechna()
    {
        $spravceAut = new SpravceAut($locator);
        $auta = $spravceAut->vratAuta(); // Proměnná pro šablonu
        require('Sablony/auta.phtml'); // Načtení šablony
    }
}

Modely / SpravceAut.php

V modeli urobíme opäť to isté, odovzdaný lokátor si preberieme a poukládáme si z neho služby, ktoré potrebujeme:

class SpravceAut
{
    private $databaze;

    public function __construct(ServiceLocator $locator)
    {
        $this->databaze = $locator->vratDb(); // Získání databáze z lokátoru
    }

    public function vratAuta()
    {
        return $this->databaze->query("SELECT * FROM auta")->fetchAll(PDO::FETCH_ASSOC);
    }

}

Problémy prístupu

Ako sme na tom po úprave aplikácie na service locator?

  • Všetky modely majú prístup ku všetkým službám v lokátora. To je síce o niečo bezpečnejšie ako u statiky a Singleton, ale stále to nie je ideálny stav.
  • Musíme stále odovzdávať inštanciu lokátora a ukladať jeho služby.

Lokátor vyjde asi na rovnaký ako statika, získali sme síce výhody, ale aj ďalšie nevýhody.

Už vás nebudeme ďalej trápiť. Budúci lekcie, Odovzdávanie závislostí pomocou Dependency Injection , obsahuje bod zlomu. Zistíme spoločný problém všetkých doteraz predstavených prístupov a jeho riešenie. Predstavíme si Inversion of Control a už dlho sľubovanú Dependency Injection.


 

Predchádzajúci článok
Zlé spôsoby odovzdávania závislostí - Statika
Všetky články v sekcii
Softvérové architektúry a dependency injection
Preskočiť článok
(neodporúčame)
Odovzdávanie závislostí pomocou Dependency Injection
Článok pre vás napísal David Hartinger
Avatar
Užívateľské hodnotenie:
1 hlasov
David je zakladatelem ITnetwork a programování se profesionálně věnuje 15 let. Má rád Nirvanu, nemovitosti a svobodu podnikání.
Unicorn university David sa informačné technológie naučil na Unicorn University - prestížnej súkromnej vysokej škole IT a ekonómie.
Aktivity