Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 5 - Špatné způsoby předávání závislostí - Singleton a SL

V minulé lekci, Špatné způsoby předávání závislostí - Statika, jsme si představili několik způsobů, kterými se lze ve vícevrstvých aplikacích vypořádat se závislostmi. Již víme, že jakmile je celá aplikace napsaná v jednom souboru, je minimálně nepřehledná, při dnešní složitosti softwaru ne-li již nevytvořitelná. Jakmile aplikaci rozdělíme na vrstvy a vrstvy na objekty, nutně začneme řešit otázku komunikace mezi těmito objekty. Když objekt komunikuje s jiným, říkáme, že má závislosti.

V dnešním tutoriálu o návrhu softwaru se budeme věnovat návrhovému vzoru Singleton, který souvisí se statikou. Tu jsme probrali minule. Dále si ukážeme vzor service locator.

Chyba č. 3 - Závislosti předáváme Singletony

Singleton, česky jedináček, je poměrně kontroverzní návrhový vzor z populární skupiny vzorů GOF. Ačkoli jej uvidíte zde na příkladu, můžete si o něm přečíst zdejší detailní článek pro případ, že byste chtěli další informace.

Singleton je principiálně podobný našemu statickému obalu z minulé lekce. Je důležité zmínit, že Singleton používá statiku, ukládá instanci závislosti do statického atributu a nabízí rovněž statickou metodu k jejímu získání. Platí pro něj tedy úplně všechny negativní vlastnosti, jako jsme si zmínili u statiky. Navíc je pravý Singleton např. v PHP poměrně složité vytvořit. Že je návrhovým vzorem jej nedělá v ničem lepším a minimálně pro předávání závislostí je to anti-pattern. Dle mého názoru je jeho použití méně vhodné, než jen čisté použití statiky. Ale to je spíše věc osobního vkusu. Singleton lze napsat thread-safe a může naleznout své uplatnění při práci s vlákny, kde jeho využití dává smysl. Na závislosti se ovšem nehodí.

Ukažme si, jak by vypadalo předávání připojené instance PDO (databáze) našim modelům v aplikaci pro evidenci automobilů.

Modely/Databaze.php

Pro získání databázové instance 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');
    }

}

Ve třídě vidíme na první pohled opět statický atribut s instancí PDO jako u wrapperu z minulé lekce. Neobalujeme tu však jednotlivé metody PDO, ale pouze poskytujeme jednu metodu pro vrácení celé instance. Povšimněte si lazy-loadingu, tedy, že se instance vytvoří až když si o ni řekneme a poté se vždy vrátí tato jedna instance. Jak atribut s PDO, tak metoda vratInstanci() jsou statické, abychom je mohli používat odkudkoli. Co ale když někdo vytvoří novou databázi zavoláním new Databaze()? Vytvářet instanci Singletonu nemá smysl, jelikož slouží pouze pro získání obsahu statického atributu. Proto tvorbu instancí třídy Databaze zakážeme a to vytvořením privátního konstruktoru. Jelikož v PHP je možné tvořit instance ještě příkazem clone nebo deserializací, musíme zakázat i tyto akce. Vidíte, že Singleton začíná být trochu magický.

Modely/Spravce­Aut.php

V modelu si jednoduše řekneme o instanci pomocí statické metody a uložíme si ji:

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);
    }

}

Již bylo řečeno, že máme stále všechny nevýhody statiky. A kód je oproti příkladu se statickým wrapperem ještě delší, ten vypadal takto:

class SpravceAut
{

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

}

Singleton nám tedy opravdu moc nepomáhá.

Kontrolery/Au­taKontroler.php

Kontroler bude identický, správce vytvoříme nového a nemusíme mu nic předávat, jelikož si databázi získá 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 přístupu

Zůstávají všechny problémy spojené se statikou:

  • Databáze je přístupná odkudkoli, tedy z kontroleru, ale i např. z šablony! Z kteréhokoli PHP souboru, který ji ani nepotřebuje. To není zrovna bezpečné a svádí to ke špatnému návrhu a porušování Low Coupling.
  • Databáze se bude velmi špatně mockovat (nahradit testovacími daty), což znepříjemní testování, až se aplikace stane větší
  • Aplikace "lže" o svých závislostech, není na první pohled patrné jaké třídy která třída používá. Abychom to zjistili, musíme projít celý zdrojový kód a všímat si statických přístupů.
  • Velkým problémem může být používání statiky ve vícevláknových aplikacích, v PHP se do této situace pravděpodobně nedostanete, ale v jiných jazycích vstoupíte do race condition. Singleton by tedy musel být navíc napsaný jako thread-safe, což ten náš není.

Máme zde další problémy:

  • Oproti statickému wrapperu musíme instanci někam ukládat, kód je zbytečně delší
  • Musíme zajistit, aby mohla existovat vždy jen jedna instance

A jednu výhodu:

  • Nemusíme wrappovat všechny metody z PDO

Singleton se na předávání závislostí jasně nehodí.

Chyba č. 4 - Závislosti sdružujeme do service lokátoru

Možná by vás napadlo vytvořit si jeden kontejner se všemi závislostmi celé aplikace a předávat všude tento jeden kontejner. Objekty si poté z kontejneru vytahají co potřebují. Tento způsob se používá např. v herním frameworku MonoGame pro C# .NET nebo v jazyce Ruby.

Vytvoříme si tedy třídu reprezentující kontejner, tam si uložíme instanci naší databáze a poté budeme všude manuálně předávat tento kontejner. Pokud bude závislostí mnoho, stále předáváme pouze jednu na kontejner. To by mělo být lepší, ne? No... Pojďme to zkusit:

Modely/Service­Locator.php

Připravme si kontejner se sdílený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

Někde na začátku aplikace si kontejner instanciujeme a následně jej musíme všude manuálně předávat:

// 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 lekce o dvouvrstvé architektuře. Tam jsme takto předávali databázi, my zde předáváme lokátor, ve kterém může být závislostí více.

Kontrolery/Au­taKontroler.php

V kontroleru si kontejner opět uložíme a budeme jej předávat všem modelům, v našem případě 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/Spravce­Aut.php

V modelu uděláme opět to samé, předaný lokátor si přebereme a poukládáme si z něj služby, které potřebujeme:

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 přístupu

Jak jsme na tom po úpravě aplikace na service locator?

  • Všechny modely mají přístup ke všem službám v lokátoru. To je sice o něco bezpečnější než u statiky a Singletonu, ale stále to není ideální stav.
  • Musíme stále předávat instanci lokátoru a ukládat jeho služby.

Lokátor vyjde asi na stejno jako statika, získali jsme sice výhody, ale také další nevýhody.

Již vás nebudeme dále trápit.

Příští lekce, Předávání závislostí pomocí Dependency Injection, obsahuje bod zlomu. Zjistíme společný problém všech dosud představených přístupů a jeho řešení. Představíme si Inversion of Control a již dlouho slibovanou Dependency Injection.


 

Předchozí článek
Špatné způsoby předávání závislostí - Statika
Všechny články v sekci
Dependency injection a softwarové architektury
Přeskočit článek
(nedoporučujeme)
Předávání závislostí pomocí Dependency Injection
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
79 hlasů
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 se informační technologie naučil na Unicorn University - prestižní soukromé vysoké škole IT a ekonomie.
Aktivity