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 4 - Špatné způsoby předávání závislostí - Statika

V minulé lekci, Třívrstvá architektura a další vícevrstvé architektury, jsme si vysvětlili třívrstvou architekturu a jak naše aplikace rozdělit na komponenty 3 typů, abychom dosáhli maximální přehlednosti a znovupoužitelnosti. Naše ukázková aplikace pro evidenci automobilů měla komponenty závislé na databázi. Databázi jsme předávali ručně přes konstruktory. I v takto krátké ukázce bylo vidět, že je to hodně práce navíc.

V dnešní lekci si ukážeme, jak závislosti, v našem případě připojenou databázi, do modelů dostaneme.

Způsoby předávání závislostí

Jakmile začnete programovat objektově, je jen otázka času, než budete někde potřebovat zavolat funkčnost mimo odpovědnost daného objektu. Jinými slovy aktuální objekt danou funkci nebude umět a ani by neměl, proto ji zavoláte na jiném objektu, který je za tuto oblast aplikace odpovědný. To jsme si již říkali v úvodní lekci. Háček je samozřejmě v tom, že onen jiný objekt může mít nějaký stav a další závislosti a proto jej buď nemůžeme přímo vytvořit nebo ani nechceme. Objekt byl totiž již vytvořen a nakonfigurován někde jinde a my jej potřebujeme odtamtud získat. Databáze je jednoduchým příkladem, její instanci nastavíme a připojíme jednou na začátku aplikace a veškerou práci s databází budeme chtít realizovat přes tuto jednu instanci.

Asi vás nepřekvapí, že programátoři za ta léta vymysleli hned několik způsobů, jak závislosti předávat. Dovolil jsem si je rozdělit do několika kategorií:

Špatné způsoby

  • Závislosti vůbec nepředávat
  • Statika
  • Singleton
  • Service locator
  • Různé variace (lokátor je Singleton a podobně)

Pracné způsoby

  • Závislosti předáváme manuálně

Správné způsoby

  • IoC (Dependency Injection)
  • Automatizace DI

Jednotlivé způsoby si během kurzu vyzkoušíme a zjistíme, co je na nich špatně. Nakonec pochopíme princip obráceného řízení a vzor Dependency Injection.

Špatné způsoby předávání závislostí

Špatná řešení jsou jednoduchá, proto jsou stále používaná, i když se dávno ví, že nejsou ideální.

Chyba č. 1 - Závislosti vůbec nepředávat

Prvním špatným řešením je závislosti vůbec nepředávat. Jakmile nějakou závislost potřebujeme, vytvoříme si její instanci znovu. Pokud na jeden dotaz na aplikaci potřebujeme 10 různých manažerů a každý potřebuje databázi, vytvoříme si instanci databáze v každém znovu, tedy celkem 10 databází a 10x se připojíme. Je snad jasné, že tento způsob není ani reálně použitelný a jedná se o odstrašující příklad. Pro úplnost si uveďme příklad.

Modely/Spravce­Aut.php

Model potřebuje databázi? Vytvoří si tedy novou instanci PDO a tu si připojí.

class SpravceAut
{
    private $databaze;

    public function __construct()
    {
        $this->databaze = new PDO('mysql:host=localhost;dbname=testdb;charset=utf8', 'root', '');
    }

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

}

Kontrolery/Au­taKontroler.php

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

    // Případné další akce jako jedno($id), odstran($id), ...
}

Všimněte si, že SpravceAut již nevyžaduje v konstruktoru databázi, protože si tvoří sám novou.

Přístup bychom mohli "vylepšit" tím, že bychom databázi tvořili v kontrolerech a následně ji předávali ručně modelům, což je takový kočkopes mezi tímto přístupem a přístupem s manuálním předáváním závislostí. Další nevýhodou tohoto přístupu je, že máme připojovací údaje k databázi na několika místech.

Problémy přístupu

Tvoření stále nových instancí je:

  • výkonově neefektivní
  • paměťově neefektivní

Máme více objektů než potřebujeme. V budoucnu by se nám stávalo, že bychom si nastavili nějaká data do jednoho objektu a následně je očekávali v instanci té samé třídy jinde. Ale tam by data samozřejmě nebyla, protože by šlo o jiný objekt.

Závislosti chceme zkrátka sdílet, potřebujeme od každé 1 jedinou instanci a k té přistupovat z různých míst aplikace.

Chyba č. 2 - Závislosti předáváme staticky

Použít statiku k předávání závislostí je pravděpodobně nejlepším ze špatných řešení. Statika je také základem Singletonu, o kterém si povíme v příští lekci. Pokud jste absolvovali místní kurzy, tak víte, že jsem se v nich k řešení pomocí statiky uchýlil. Napsat si vlastní DI kontejner je totiž práce pro expertní programátory a používat složité cizí frameworky hned ze začátku také není velkou výhrou. S něčím se začít musí, ale zároveň bychom u statiky neměli příliš dlouho zůstávat.

Víme, že statické atributy patří třídě, nikoli instanci. A třída je globálně viditelná. Co si uložíme do statických atributů bude tedy vždy přístupné odkudkoli. Sice to není přesně to, co potřebujeme, ale z jednotlivých modelů se ke staticky uloženým závislostem dostaneme. Existuje mnoho způsobů, jak pomocí statiky závislosti předávat. Nejlepší implementací je pravděpodobně:

  • Manažery tvořit stále znovu
  • Jakmile manažer potřebuje nějakou závislost, přistoupí k jiné třídě staticky. Klíčové závislosti, jako je např. databáze, můžeme napsat celé staticky, to si ukážeme za chvíli. Menší závislosti, např. instanci aktuálně přihlášeného uživatele, můžeme uložit do statického atributu, např. na třídu SpravceUzivatelu, aby byla tak přístupná i ostatním třídám.

Modely/Databaze.php

Pro databázovou třídu PDO z PHP si vytvoříme statický wrapper (obal). Tím ji bude možné používat kdekoli bez omezení a zároveň se bude sdílet vždy jen 1 připojení. Zdrojový kód třídy by vypadal takto:

class Databaze
{
    private static $pdo;

    public static pripoj($db, $jmeno, $heslo)
    {
        self::$pdo = new PDO("mysql:host=localhost;dbname=$db;charset=utf8", $jmeno, $heslo);
    }

    public static function dotazVsechny($dotaz) // Parametry nebudeme pro jednoduchost řešit
    {
        return self::$pdo->query($dotaz)->fetchAll(PDO::FETCH_ASSOC);
    }
}

Všimněte si, že všechny metody i atributy třídy reprezentující klíčovou závislost jsou statické. To abychom instanci vůbec nepotřebovali a nemuseli ji předávat.

index.php

Někde na začátku aplikace, v index.php, bychom databázi měli připojit:

// Autoloader
// ...

Db::pripoj('testdb', 'root', '');

// Nějaké volání routeru, který spustí příslušný kontroler
// ...

Připojená instance PDO se uložila do statického atributu na třídě Databaze.

Modely/Spravce­Aut.php

Nyní můžeme odkudkoli zavolat metodu Databaze::dotazVsechny(), která obaluje metodu query() na připojené instanci PDO:

class SpravceAut
{

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

}

Model SpravceAut má k databázi přístup.

Kontrolery/Au­taKontroler.php

A co kontroler? Tam opět nemusíme nic řešit, pouze vytvoříme novou instanci SpravceAut.

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

V celé aplikaci se nám sdílí jedna připojená instance databázové třídy PDO díky faktu, že jsme ji obalili statickou třídou Databaze, která má atribut statický.

Problémy přístupu

Co je tedy špatně?

  • 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.

Statika má nápadně blízko k globálním proměnným, které jsou antipattern a moderní jazyky je již ani nepodporují. Aby nedošlo k nedorozumění, zopakuji ještě na závěr, že statické předávání závislostí je občas použito v místních kurzech kvůli své jednoduchosti. V pokročilejších kurzech se poté pracuje s frameworky, které tuto problematiku řeší lépe.

Pro dnešní lekci je to vše.

Příště, v lekci Špatné způsoby předávání závislostí - Singleton a SL, si ukážeme problémy Singletonu a service locatoru, čímž dokončíme špatné přístupy předávání závislostí a budeme se moci pustit do DI :)


 

Předchozí článek
Třívrstvá architektura a další vícevrstvé architektury
Všechny články v sekci
Dependency injection a softwarové architektury
Přeskočit článek
(nedoporučujeme)
Špatné způsoby předávání závislostí - Singleton a SL
Článek pro vás napsal David Hartinger
Avatar
Uživatelské hodnocení:
77 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