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::$instan­ce == 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_Excep­tion(„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(Zamestna­nec $zam) { /
smaze zamestnance – muze vyhodit vyjimku ZamestnanecEdi­tException / }
public function save(Zamestnanec $zam) { /
ulozi zmeny daneho zamestnance / }
public function find($cisloZa­mestnance) { /
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 ZamestnanciEdi­taceFacade {
/
 * @var ZamestnanecEdit
 /

private $zamestnanecE­ditace;
/

 
@var StrediskoEdit
 */

private $strediskoEdi­tace;

public function __construct() {
  $this->zamestnanecE­ditace = new ZamestnanecEdi­tace();
  $this->strediskoEditace = new StrediskoEdita­ce();
}

public function addZamestnanec(Za­mestnanec $zamestnanec) {
    $this->zamestnanecE­ditace->add($zamestna­nec);
}

public function addStredisko(Stre­disko $stredisko) {
   $this->strediskoEditace->add($stredis­ko);
}
// .....

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.

Pokud budeme mluvit o objektech jako o zapouzdřených componentách, musíme nějakým způsobem zajistit, aby daná komponenta poskytovala jen to, co skutečně má. Pokud bychom ignorovali viditelnost jako takovou, vznikl by nám mezi jednotlivými třídami chaos, který by zapříčinil nepoužitelnost.

Vezmu to pěkne od začátku, mám atributy třídy a metody třídy. Obě tyto vlastnosti každého objektu mají volitelnou viditelnost, která je rozdělena do tří úrovní:
  • public
  • protected
  • private

Public
Jakákoli vlastnost objektu nastavená viditelností public mi umožňuje říci, že: „každý má právo k této vlastnosti přistupovat“.

Protected
Narozdíl od public, má protected omezení jen na viditelnost v samotné třídě a třídách, které jsou potomkem.

Private
K vlastnostem označeným jako private může přistupovat pouze a jedině vlastní třída.

Když jsem v minulých dílech tvrdil, že atributy třídy by měly být označeny vždy jako private, neříkal jsem úplně pravdu. Při tvorbě vlastních tříd se dost často setkáme s problémem, kdy bych potřeboval použít daný atribut jako parametr metody. Jinými slovy řečeno, podle parametru mi metoda vykoná to či ono.

class HTML_Form_Input {
       private $value;
       .....
       public function __construct($va­lue) {
              $this->value = $value;
       }

       public function setValue($valu­e) {
              $this->value = $value;
       }

       public function getValue() {
              return $this->value;
       }
       .....
 }

Tato jednoduchá třída implementovala private jako viditelnost pro atribut třídy a public jako atribut pro konstruktor a setry a getry. Teď ovšem nastává problem, kdy bych potřeboval identifikovat typ samotného inputu. Jedna z možností by byla, že bych metodě setType(String) vždy posílal vlastním stringem, o jaký typ se vlastně jedná. Toto sice lze, ale my se budeme snažit o co nejlepší znovupoužitelnost a možnost nastavení těchto typů přímo ve třídě, abychom nemuseli při refactoringu dohledávat všechny různé překlepy, atd.

class HTML_Form_Input {
       const TYPE_HIDDEN = „hidden“;
       …
}
$input = new HTML_Form_Input(„jme­no“);
$input->setType(HTML_In­put_Form::TYPE_HID­DEN);
  

Konstanta jako taková je neměná a je přístupná mimo vytvořený objekt. Pokud bych chtěl danou konstantu volat přímo ve třídě, využiji k tomu self::.

S dalším klíčovým slovem, se kterým se můžete setkat, je static. Statický atribut či metoda je taková vlastnost, která mi zapříčiní, že ji mohu získat aniž bych musel vytvářet objekt. Její hodnota je tedy nezávislá na tom, zda objekt existuje či nikoli. Pokud bych to převedl do praxe, můžu pomocí statického atributu třídy zjišťovat, kolikrát jsem vytvořil objekt z dané třídy.

Další ukázky si nechám do příštího dílu, který budu věnovat Design Patterns, neboli návrhovým vzorům. Ukážu např. implementování Iteratoru, který lze nativně v PHP 5 nalézt.

Když se vrátím na začátek, kde jsem tvrdil, že objekty jsou jakýsi obal nad procedurálním kódem, tak se musím zamyslet nad tím, jak umožnit, abych nemusel stále dokola zabalovat jednu a tu samou věc. Třídy bych měl vždy navrhovat tak, aby obsahovali jen to nejnutnější a nebyly zbytečně vázany na jiné třídy. Navíc třídy by něměli obsahovat pevnou vazbu na třídy, které jsou navrženy v jiných úrovních.

Příklad:
Do třídy pro zobrazení stromové struktury nebudu cpát třídu, která mi vrací data stromu. Raději ve třídě stromu napíši metodu, která bude umět přijmout data a zobrazit je. Na získání dat vytvořím jinou třídu.
Když budu později potřebovat zobrazit strom s jinými daty, prostě použiji již hotovou třídu stromu, která přijímá data.

Tím dokážu vytvořit znovupoužitelný kód. K tomu, abych mohl takto tvořit třídy, ale musím znát všechny možnosti objektů. Nyní se vratím k další možnosti objektů a tou je dědičnost.

Dědičnost je vlastnost, která mi umožňuje rozšířit třídu a pozměnit její chování. Nic víc, nic míň.

Na začátku jsem napsal, že třídy by měli být co nejořezanější, neměli by obsahovat věci, které by ji svázali natolik, že by obsahovala zbytečné a nevyužitelné vazby. Pokud ovšem v projektu potřebuji danou třídu rozšířít a to tak, že dané rozšíření budu využívat vícekrát, prostě napíši si novou vlastní třídu, která bude dědit vlastnosti z minulé třídy.

Příklad:
Mám třídu na zobrazování filtrace nad daty. Teď je ovšem problém, že v některých případech filtruji přes ty samé položky (př. název). Jednou možností je, pořád do objektu posílat objekt (komponentu) název. Jelikož už vím, co je dědičnost, mohu ji využít. Vytvořím novou třídu, která bude dědit třídu filtrace a navíc bude automaticky vkládat komponentu pro název.

Toto je jedna z možností jak dědičnost využít, přiznávám, že ne moc efektivní, jistě existují i jiné možnosti, ale jako příklad je to postačující.

Asi lepším příkladem je struktura dat v databázi. Dnes jsou nejrozšířenejší relační typy databází. Jistě jsou velmi efektivní, rychlé a snadné na práci, ale pro nás jsou dost nevyhovující, proto existuje ORM (objektově relační mapování), což není nic jiného, než, že tabulku v databázi převedeme na objekt.

CREATE TABLE zamestnanec (
cislo int NOT NULL,
jmeno varchar(100) NOT NULL,
prijmeni varchar(100) NOT NULL,
PRIMARY KEY(cislo)
 );
class Zamestnanec {
      private $cislo;
      private $jmeno;
      private $prijmeni;
      /* + setry a getry ke vsem atributum */
 }

Nyní vytvoříme tabulku „uzivatele“. U uživatelů budeme potřebovat navíc heslo, počet přihlášení, poslední přihlášení. V databázi tedy vytvoříme tabulku „uzivatele“, která bude mít primární klíč osobní číslo, které bude zároveň cizím klíčem s vazbou do tabulky zaměstnanců na osobní číslo.

Potom pro výpis uživatelů použijeme něco takového:
SELECT u.*, z.jmeno, z.prijmeni
FROM uzivatele u
INNER JOIN zamestnanci z ON u.cislo = z.cislo

To je v pořádku, databázi máme, ale jak to uděláme v našem projektu, kde máme objekty? Velice jednoduše, použijeme dědičnost.

class Uzivatel extends Zamestnanec {
      private $heslo;
      private $pocetPrihlaseni;
      private $posledniPrih­laseni;
      /* setry a getry */
 }

Nyní jsem vlastně rozšířil zamestnance o další atributy aniž bych musel znovu psát jeho vlastnosti (např. v Jave na toto existují tzv. entity classes, u nich by bylo toto rozdělení více podobné struktuře DB, což v PHP zařídit jen tak nepůjde). Dědičnost se definuje tak, že mám jednoho předka, který obsahuje vlastnosti, které se mi budou hodit i ve třídě, která je dědí. Pokud by se mi stalo to, že budu dědit z třídy, která obsahuje vlastnosti, které nebudu nikdy potřebovat, je dědičnost navržena špatně.

Tento článek berte spíše s nadhledem, opravdu se domnívám, že dědičnost je tak záludná záležitost, že bych se na ni na začátku úplně vykašlal. V pozdějších článcích se k tomto tématu vrátím a ukážu, jak něco takového využít v praxi. Příklady, které jsem zde uvedl nejsou úplně nejtypičtější, ale jistě lepší, než ukázky typu: Matka ⇒ Dcera, které postrádají použitelnost v praxi.

Příště už o něčem jednoduchém, viditelnosti a klíčových slovech static, final, apod.

© 2007 finc weblog | iKon Wordpress Theme by Windows Vista Administration | Powered by Wordpress