úterý 6. března 2007

OOP v PHP 5 - teorie dědičnosti a ORM

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 $posledniPrihlaseni;
/* 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.

12 komentářů:

  1. A nemas odzkousene nejake hotove ORM v PHP?

    OdpovědětVymazat
  2. Mám své vlastní :)
    Rád bych ho zde právě předvedl. V PHP toto nelze až ta dobře provozovat, důvody jsou myslím jasné, už z toho důvodu, že není objektově koncipováno.
    Jednou z možností je použití PROPEL, kde lze nalézt jak ORM tak DAO.

    OdpovědětVymazat
  3. V praci take pouzivame vlastni ORM, ale pro soukrome ucely se me nechce psat vlastni.

    Treba by nebylo spatne ten serial koncipovat jako projekt. Das zadani a vypracovavas. Pritom vysvetlujes ruzne veci.

    Nekolikatero knizek jsem takto cetl a bylo to dobre, Drzi to ctenare pri cteni.

    OdpovědětVymazat
  4. Nemas pls na sebe email, rad bych se stebou na necem domluvil. Muj je error414@error414.com

    OdpovědětVymazat
  5. Muj mail je a.dostal[paznak]gmail.com

    OdpovědětVymazat
  6. akova rejpava pripominka :D.

    trida class ma atributy private, a pak ji dedis. Jelikoz mas v te tride i setry a getry tak se k tem vlastnostem dostanes.

    class Zamestnanec {
    private $cislo;
    private $jmeno = 'petr';
    private $prijmeni;


    public function getJmeno(){
    return $this->jmeno;
    }
    }

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

    $p = new Uzivatel();
    echo $p->getJmeno();


    Tohle je v poradku. Ale predstav si ze potrebujes zmenit metodu getJmeno().

    class Zamestnanec {
    private $cislo;
    private $jmeno = 'petr';
    private $prijmeni;


    public function getJmeno(){
    return $this->jmeno;
    }
    }

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

    public function getJmeno(){
    $prefix = "LOD";
    return $prefix.$this->jmeno;
    }
    }

    $p = new Uzivatel();
    echo $p->getJmeno();

    Tohle uz zhavaruje. Podle me je to hodne matouci. Napada me akorat pouzit u setru a geteru pouziti final.


    class Zamestnanec {
    private $cislo;
    private $jmeno = 'petr';
    private $prijmeni;


    final public function getJmeno(){
    return $this->jmeno;
    }
    }

    BTW: PHP ma paradni objektovy model

    OdpovědětVymazat
  7. zdravim, chybi mi tady nejake blizsi popsani fungovani dedicnosti digestore, vezmeme-li v uvahu proceduralni fungovani javy.

    OdpovědětVymazat
  8. Abych řekl pravdu, myslím že znovu definovat proměnné v dědičné třídě se nemusí. Nebo nemám pravdu?
    Příklad:
    class sql {
    var $query;

    function dotaz() {
    return mysql_query($this->query);
    }
    }
    class zkouska extends sql {
    $this->query = "select bla bla bla";
    $this->dotaz();
    }

    OdpovědětVymazat
  9. Petulan javu bych do php nepletl, ale jinak souhlasim.

    OdpovědětVymazat
  10. to error414:
    To, že jsou nastaveny private je správně. Přistupovat k atributům třídy by nemělo být přímé, ale právě přes setry a getry. To co popisuješ bych asi řešil vlastní třídou, jinak stále mám možnost použít přepsání prvotní metody, ale s tím, že musím samozřejmě vytvořit vlastní atribut. Nebo atributy nastavit na protected :)

    to Petulan:
    Jsi to moje chytrá dívka ;)

    OdpovědětVymazat
  11. to JakubVrabec:
    Právě že musí. To, co popisujš ty, je starý kód z PHP4, kde neexistovala viditelnost (private, protected, public), tak jistě setry a getry téměř ztráceli smysl, jelikož to stejně vývojáře tlačilo k použití atributu napřímo.

    class Zvyky {
    private $pozdrav;
    public function __construct() {
    $this->pozdrav = "ahoj";
    }
    }

    class NoveZvyky extends Zvyky {
    public function __construct() {
    parent::__construct();
    // k atributu pozdrav nemam jiz pristup
    echo $this->pozdrav;
    }
    }

    OdpovědětVymazat
  12. Ja bych tam stejne radeji dal jen chrane (protected) abych se vyhnul problemum

    OdpovědětVymazat

Když programátor založí a řídí firmu

Jako malý jsem chtěl být popelářem. Ani ne tak proto, že bych měl nějaký zvláštní vztah k odpadkům, ale hrozně se mi líbilo, jak...