Přeskočit na hlavní obsah

OOP v PHP5 díl.7 - návrhové vzory (design patterns)

Při programování objektovým způsobem se dost často dostanete do fáze, kdy si nebudete jistí, jak máte danou třídu či samotnou práci s instancí provést. Může se jednat o triviální problémy typu: připojení k DB či složitější věci jako návrh MVC Frameworku.

Na tyto problémy mysleli čtyři chlapíci (Erich Gamma, Richard Helm, Ralph Johnson a John Vlissides), kteří jsou autory knihy Design Patterns: Elements of Reusable Object-Oriented Software. Kniha se brzy stala natolik oblíbenou, že je dodnes povážována za nejlepší dílo ohledně OOP. Více o této historii se můžete dozvědět např. na Wikipedii.

Samotné návrhové vzory nejsou standardem, ale doporučením, jak při psaní máte postupovat. Já se nyní pokusím sepsat, jak je to
s návrhovými vzory v PHP. Vyberu jen ty nejjednodušší a nejčastěji používané a ukáži, jak je to s jejich implementací.

1. Singleton (jedináček)

Jak již název napovídá, jedná se o objekt, který je vytvořen pouze jednou. Tento návrhový vzor můžete využít například u připojení k databázi, kde si jasně řeknete, že byste neradi, aby Vám PHP otevíralo více spojení.

class Pripojeni {
private static $instance = null;
private function __construct() {}
public static function getInstance() {
if (self::$instance == null) {
$class = __CLASS__;
self::$instance = new $class;
}
return self::$instance;
}
}


Samotný fígl spočívá v tom, že instance samotného objektu je uložena jako statický atribut třídy a objekt je vytvořen pouze v případě, že je atribut null. Konstruktor třídy je nastaven jako private, aby se nikdo nemohl snažit vytvořit objekt pomocí new Pripojeni();. Samotní vývojáři v PHP ale přišli na to, že Singleton nelze na 100% vytvořit. Důvody proč tomu tak je, můžete najít v PHP dokumentaci, zejména v komentářích.

2. Factory

Tento vzor slouží k tomu, že vytvořím jednoduchou třídu, ve které vytvořím metodu, která podle přijímaného parametru vrací ten či onen objekt.

class Factory {
const MYSQL = "mysql";
const ORACLE = "oracle";
public static function getDBMS($dbms) {
if ($dbms == self::MYSQL) {
return new MySQL();
} else if ($dbms == self::ORACLE) {
return new Oracle();
} else {
throw new Factory_Exception("Neznamy ovladac");
}
}
}


Z kódu je jasné, že samotný vzor lze použít například k připojení na ruznorodé databáze.

3. Transfer object

Pokud skutečně chceme programovat objektově a programovat co nejefektivněji, nevyhneme se tomuto vzoru. Jedná se v podstatě o jednoduché třídy, které mají za úkol preprezentovat přenášená data. Nějčastěji se můžeme s tímto vzorem setkat v ORM nástrojích, kde jsou jednotlivé tabulky mapovány jako třídy a jedna instance je ekvivalentní jednomu řádku v databázi.

class Zamestnanec {
private $osobniCislo;
private $jmeno;
private $prijmeni;
/**
* @var Stredisko
*/
private $stredisko;
// ... setry getry


Často se také můžeme setkat s tím, že samotné objekty jsou příliš složité (tzn. odkazují se na další objekty jako v případě zaměstnance a střediska). Pokud se tak stane a naše aplikace přenáší velké množství dat, je nasnadě použít tzv. DTO (data transfer object), což je v podstatě jednoduchý objekt, který nemá jako atributy třídy další objekty, ale primitivní datové typy. V případě zaměstnance by to bylo číslo střediska, což je primární a unikatní klíč střediska. O tom, jak tvořit samotné DTO je spíše otázka té či oné aplikace a její složitosti a náročnosti.

4. Facade

Při vývoji softwaru se programátoři dostávali do situací, kdy potřebovali mít jednotlivé vlastnosti na jednom místě. Ať už z důvodu přehlednosti, rychlosti či provázanosti. Vzor facade řeší roztříštěnost vaší aplikace. Představme si, že máme vytvořit modul na správu zaměstnanců, která bude řešit jak editace zaměstnance, tak třeba i střediska. Jedna z možností je, že budeme zpětně složitě dohledávat objekty na editaci jednotlivých kategorií a nebo si vytvoříme jasně definovaná pravidla:
class ZamestnanecEdit {
public function add(Zamestnanec $zam) { /* prida zamestnance */ }
public function remove(Zamestnanec $zam) { /* smaze zamestnance - muze vyhodit vyjimku ZamestnanecEditException */ }
public function save(Zamestnanec $zam) { /* ulozi zmeny daneho zamestnance */ }
public function find($cisloZamestnance) { /* return Zamestnanec */ }
public function findAll() { /* vrati array vsech zamestnancu */ }
}


Tento jednoduchý objekt pracuje se zamestnanci. Třídu na editaci středisek si jistě dovedete představit. Nyní se vrátím k zadanému úkolu. Naprogramovat modul pro editaci zamestnance i s editaci střediska. Jedna z možností je, na view vrstvě volat tyto třídy pro práci a nebo si pěkne zhlukovat dané objekty do jednoho.
class ZamestnanciEditaceFacade {
/**
* @var ZamestnanecEdit
*/
private $zamestnanecEditace;
/**
* @var StrediskoEdit
*/
private $strediskoEditace;

public function __construct() {
$this->zamestnanecEditace = new ZamestnanecEditace();
$this->strediskoEditace = new StrediskoEditace();
}

public function addZamestnanec(Zamestnanec $zamestnanec) {
$this->zamestnanecEditace->add($zamestnanec);
}

public function addStredisko(Stredisko $stredisko) {
$this->strediskoEditace->add($stredisko);
}
// .....


Samozřejmě by se dalo najít spoustu příkladů použití. Pokud při programování někdo přemýšlí hlavou, dojde mu, že takto postupovat, znamená, že co nejvíce odstinuji view vrstvu od aplikační logiky.

5. Iterator

Posledním návrhovým vzorem, kterým se zde budu zabývat je Iterator. Již dříve jsem psal, že PHP 5 má k dispozici rozhranní Iterator, které podporuje tento návrhový vzor. Takže, oč jde. Nejednou se stane, že potřebujeme na určitou kolekci dat použít nejaký cyklus, ve kterém bych mohl daná data procházet. Na jednu stranu, mohu sice použít vlastní logiku na procházení daty, ale také mám možnost připravit si jakousi formičku pro určitá data, která se budou procházet. Příkladem může být, že vytvořím objekt s počátečním datumem (1.2.2007) a konečným datem (5.7.2007) a budu chtít daná data procházet po dni. Nebo budu chtít procházet písmena od B do X po jednotlivém pismenu. Na tyto a další operace se náramně hodí použít tzv. Iterator. Iterátor jako takový má podle interface přesně definované metody, které musí třída implementující rozhranní Iterátor obsahovat:

  • rewind() - nastavuje na první položku v seznamu

  • current() - vrací aktuální položku seznamu

  • key() - vrací klíč pole

  • next() - vrací následující položku seznamu

  • valid() - zjišťuje, zda existuje ještě nějaká položka


Podle těchto jasných pravidel jsem schopen vytvořit třídu, která bude procházet libovolný seznam dat podle určitých kriteríí či podle vlastní logiky. Velkou výhodou je znovupoužitelnost a také zapouzdřenost. Jiný programátor, kterému tuto třídu poskytnete vůbec nemusí vědět, jak daná logika pracuje, stačí, že implementuje Iterator a již je schopen, podle zadaných vstupních parametrů s třídou pracovat.

Návrhových vzorů samozřejmě existuje mnohem více. Navíc PHP je objektově dost omezené, takže ne vždy je zrovna ideální řešení právě ten či onen vzor. Někdy bývá lepší si některé věci prostě naprogramovat po svém. Na druhou stranu, bývá dobrým zvykem, že pokud se budu řídit určitými kriterii, které jsem v minulých článcích popsal, měl by vývojář míti klidné spaní. Na mysli mám zejména dodržování specifik jako jsou setry, getry, nazvosloví či dokumentace.
I když není objektově orientované programování zrovna jednoduchá záležitost, tak věřím, že pro nikoho není překážkou se základní principy naučit.

To by bylo k PHP asi tak vše. Jelikož jsem slíbíl, že daný díl dopíšu, stalo se tak. Nyní jsem již situován jiným směrem, takže PHP je pro mě pouze vedlejší záležitost. Přeji mnoho úspěchů při objektovém programování, ať už v jakémkoli jazyce.

Poslední radou by mělo být asi následující: OOP není cíl, ale prostředek k dosažení cíle. Proto neberte OOP jako něco spásného. Ba naopak, dost často se dostanete do problémů, které nebude jednoduché vyřešit.

Komentáře

  1. Děkuji, že jste nakonec tento článek vydal.

    OdpovědětVymazat
  2. Jakub Podhorský6. června 2007 10:55

    taky děkuju za vydání článku ale rád bych se ještě přimluvil za článek o dalších design patternech :) i když s prosením toho už moc nezmůžu :)

    OdpovědětVymazat
  3. Co přesně máš na mysli tím, že Singleton nejde v PHP vytvořit? Tu věc, že při každém požadavku se vytváří znovu, nebo něco jiného?

    OdpovědětVymazat
  4. Mam na mysli dedicnost. I kdyz me momentalne nenapada duvod proc dedit singleton, tak pri dedicnosti nejsem schopen zajistit, aby se vytvorila pouze jedna instance jedinacka.

    OdpovědětVymazat
  5. Ja bych teda klidne dedila...
    Jo, a Petulan neni jedinacek a zadnou instanci nepotrebuje. HAJFOOO!

    OdpovědětVymazat
  6. to Petulan: Ja bych s tim dedenim jeste par let pockal :)

    OdpovědětVymazat
  7. K proměnné, nesoucí prvek instance, se u singletonu nikde mimo metodu getInstance nepřistupuje přímo. Proto je zbytečné, aby překážel ve jmenném prostoru třídy a je podle mě vhodnější statická proměnná uvnitř metody:

    /---code php
    public static function getInstance() {
    static $instance;
    if (is_null($instance)) {
    $className = __CLASS__;
    $instance = new $className;
    }
    return $instance;
    }
    \---

    Stejně jako kdysi v PHP4... Nebo má ta členská proměnná nějaké opodstatnění?

    OdpovědětVymazat
  8. Mimochodem, trochu s křížkem po funuse, ale stejně:

    Vytvoření singletonu lze zkrátit na jeden řádek:
    /---code php
    self::$instance = new self;
    \---

    OdpovědětVymazat
  9. to LLook: nemuze to byt uvnitr metody, musi to byt globalne pro danou tridu, protoze kdyz se poprve vytvori instance tridy, tak se tato instance priradi do promenne
    self::$instance = new $class;

    kdyz se pozdeji zavola Pripojeni->getInstance(); tak se zjisti, ze promenna $instance neni null a tak vrati promennou $instance (tedy jiz vytvoreny objekt)

    OdpovědětVymazat

Okomentovat

Populární příspěvky z tohoto blogu

Jak si v IT vydělat hodně peněz?

Na začátek by bylo dobré, abych objasnil samotný titulek, který může na někoho působit jako červený hadr. Článek nebude o obecných pravidlech, ale bude vyprávět můj vlastní příběh, na kterém vám zkusím ukázat, jak se dá docílit úspěchu, či alespoň správně nastartovat svojí vlastní kariéru v IT.

I když se z názvu článku dá dedukovat, že se vše bude točit kolem peněz, není tomu tak. Alespoň ze dvou třetin určitě ne. Ale to už předbíhám, pojďme to raději vzít hezky popořadě...

Kdybychom měli mluvit o roce 2017 jako o přelomové době, nejspíše to nebude pravda. I když pro někoho to může být rok plný úspěchů a štěstí v podobě narození zdravých dětí, svatby či první velké lásky, tak z pohledu lidstva se jedná o rok, který jen kopíruje předešlé a v oblasti technologií nás posouvá stejným tempem jako rok předtím.

Jsem naprosto přesvědčen o tom, že i když se současná doba tak nenazývá, tak prožíváme dobu, která jednou bude označena za revoluční, a to zejména díky vynálezu internetu, který je st…

Jak by se firmy neměly chovat k programátorům?

Každý, kdo pracuje v IT oboru, se jistě již setkal s různými „geniálními nápady“, od kterých si firma slibovala zlepšení produktivity či snížení nákladů. Ať už je to zavedení agilních principů, striktní kontrola práce či zavedení nové a skvělé metodiky, o které si „šéf“ přečetl včera na internetu. Jsou z toho skutečně tak nadšení i samotní vývojáři? A bude nový nápad fungovat?
K napsání tohoto článku mě navedly různé programátorské diskuze, kde si lidé stěžovali na firmu, kde pracují. Příklady, které zde uvedu, jsou z reálné praxe. Ať už jsem je zažil jako řadový programátor, či jako šéf týmu.
I když je poptávka po programátorech tak vysoká, že Vás headhunteři nahánějí i ve chvílích, kdy o to opravdu nestojíte, tak i přes to je mnoho lidí, kteří se bojí opustit svoje současné zaměstnání.
Čeho se nejčastěji bojíme? Je to samozřejmě nejistota, kterou si často omlouváme větami jako: „Tady mám své pohodlí, co když to jinde mít nebudu?“ nebo „I když mě to v práci štve a nebaví, tak mě ale…

Jak jsem technologicky postavil startup

Tento příběh pojednává o technologiích, nástrojích a vůbec o všem, co jsem potřeboval k tomu, abych byl schopen, postavit startup na zelené louce.

Každý správný příběh začíná stejně: "Jednou jsem...."

Kapitola první: Nápad
Jednou jsem se setkal s člověkem, který měl nápad na produkt, který se v průmyslu zatím nevyskytuje. I přes prvotní skepsi, kdy jsem si říkal: "Tohle už přeci dávno v průmyslu existuje, ne?", jsem došel ke zjištění, že nikoli.

Tím jsem se dostal ke svému prvnímu poučení. Průmysl je technologicky dost zabržděný. Osobně se domnívám, že těch důvodů, proč tomu tak je, je několik. Za prvé je to fakt, že většina lidí, kteří se pohybují v tomto odvětví jsou často konzervativní a za správné považují pouze léty osvědčené věci. Druhým důvodem je to, že jakákoli změna znamená riziko. Ať už z pohledu finanční ztráty tak i z pohledu stability výroby. No a třetím a nejzásadnějším důvodem je to, že ač zde máme spousty technologických vymožeností, narážíme na to,…