úterý 5. června 2007

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.