8. díl - Polymorfismus, finální prvky a autoloader v PHP

PHP Objektově orientované programování Polymorfismus, finální prvky a autoloader v PHP American English version English version

V minulém tutoriálu o objektově orientovaném programování v PHP jsme si vysvětlili dědičnost a oddědili jsme si od člověka javistu. Dnes budeme v podobném duchu pokračovat a uvedeme si tzv. polymorfismus.

Polymorfismus

Nenechte se vystrašit příšerným názvem této techniky, protože je v jádru velmi jednoduchá. Polymorfismus umožňuje používat jednotné rozhraní pro práci s různými typy objektů. Mějme například mnoho objektů, které reprezentují nějaké geometrické útvary (kruh, čtverec, trojúhelník). Bylo by jistě přínosné a přehledné, kdybychom s nimi mohli komunikovat jednotně, ačkoli se liší. Můžeme zavést třídu GeometrickyUtvar, která by obsahovala atribut $barva a metodu vykresli(). Všechny geometrické tvary by potom dědily z této třídy její interface (rozhraní). Objekty kruh a čtverec se ale jistě vykreslují jinak. Polymorfismus nám umožňuje přepsat si metodu vykresli() u každé podtřídy tak, aby dělala, co chceme. Rozhraní tak zůstane zachováno a my nebudeme muset přemýšlet, jak se to u onoho objektu volá.

Polymorfismus bývá často vysvětlován na obrázku se zvířaty, která mají všechna v rozhraní metodu speak(), ale každé si ji vykonává po svém.

Polymorfismus

Podstatou polymorfismu je tedy metoda nebo metody, které mají všichni potomci definované se stejnou hlavičkou, ale jiným tělem. Vyzkoušejme si to na našich lidech.

Pozdrav

Člověk má definovanou metodu pozdrav(), která pozdraví tímto způsobem:

Ahoj, já jsem Jan Novák

Tuto metodu dědí potomci člověka, tedy i javista, kde se zatím metoda chová stejně. My jsme však schopni zděděnou metodu přepsat tak, aby potomek zdravil jinak, než člověk.

Přepsání metody

Přepsání metody je velmi jednoduché, stačí ji v potomkovi pouze znovu definovat. Naučme tedy javisty zdravit nějakým programátorským způsobem a přidejme metodu pozdrav() do třídy Javista:

public function pozdrav()
{
        echo('Hello world! Jsem ' . $this->jmeno);
}

Pojďme si to vyzkoušet do index.php. Abychom plně pocítili výhody polymorfismu, uděláme si několik instancí lidí a javistů a ty si vložíme do pole:

$lide = array();
$lide[] = new Clovek('Karel', 'Novák', 30);
$lide[] = new Javista('Jan', 'Nový', 24, 'Eclipse');
$lide[] = new Clovek('Josef', 'Nový', 50);
$lide[] = new Javista('Tomáš', 'Marný', 28, 'NetBeans');
$lide[] = new Clovek('Marie', 'Nová', 32);

Nyní budeme chtít, aby všichni lidé pozdravili. Jelikož jsme použili polymorfismus, tak na všech instancích zavoláme pozdrav tím samým způsobem. Každá instance potom pozdraví tak, jak to má naprogramované.

foreach ($lide as $clovek)
{
        $clovek->pozdrav();
        echo('<br />');
}

Výstup programu bude následující:

Polymorfismus v PHP

Přepsání metody najdeme v anglické literatuře pod názvem override.

Výhody polymorfismu

Polymorfismus je velmi čistá programátorská technika. Všimněte si, že jsme se vyhnuli jakémukoli větvení. Nemusíme nikde ifovat jestli je instance člověk nebo javista. Představte si, že bychom podobných potomků lidí měli 100, větvení by potom bylo velmi nepřehledné. Takhle je konkrétní logika pro určitý typ člověka vložena v jeho třídě, kam logicky patří. Hromadná práce s instancemi různých typů nebyla nikdy snadnější. Využití samozřejmě nalezneme i při práci jen s jednou instancí, kde se opět vyhneme větvení.

Potomek se může rozhodnout, zda si metodu přepíše po svém nebo si nechá tu zděděnou. Pokud budete psát programy objektově, často se podobným způsobem vyhnete jinak složitým konstrukcím, jako je dlouhé větvení, vnořené cykly a další.

Klíčové slovo final

V PHP můžeme použít klíčové slovo final, které umožňuje zakázat dědění tříd nebo přepisování metod. Ačkoli je praktický význam této techniky diskutabilní, jako vždy ji zde uvádím pro případ, že byste se s ní někdy setkali.

Finální metody

Přepsání nějaké metody můžeme v nadtřídě zakázat označením metody jako finální. Potomek potom nebude schopen metodu přepsat. Můžeme si to zkusit ve třídě Clovek na metodě pozdrav():

public final function pozdrav()
{
        echo('Ahoj, já jsem ' . $this->jmeno);
}

PHP nyní vyhodí při spuštění skriptu Fatal error, jelikož se potomek Javista snaží přepsat onu finální metodu:

Cannot override final method Clovek::pozdrav() in...

Slovo final z definice metody zas odebereme.

Finální třídy

Jako finální můžeme označit i celou třídu. Zakážeme tím kompletně dědičnost a tato třída poté nemůže mít potomky. Opět si to zkusme na třídě Clovek:

final class Clovek
{

        ...

}

Opět dostáváme Fatal errorm jelikož Javista se snaží z člověka dědit:

Class Javista may not inherit from final class (Clovek) in...

Význam finálních prvků

Jak již bylo zmíněno, význam finálních prvků je poněkud pochybný. Původní myšlenkou bylo zakázat dědění nebo přepisování určitých prvků aplikace. To může být užitečné u velkých frameworků se spoustou tříd, kde tak násilně zabráníme dalšímu zesložiťování aplikace. Prakticky však může mít neuvážené použití slova "final" nepříjemné následky, kdy nebude možné využít výhod dědičnosti a nevyhneme se psaní redundantního kódu. Doporučoval bych slovo final vůbec nepoužívat.

Automatické načítání tříd

Na konec si ukažme vychytávku, díky které nebudeme muset manuálně requirovat třídy. Je to velmi užitečné hlavně ve chvíli, když jich máme hodně a také proto, že si nemusíme hlídat, zda vše, co používáme, také načítáme. To bývá někdy zdrojem nepříjemných chyb.

PHP ve starších verzích používalo k automatickému načítání tříd magickou metodu __autoload. Jelikož již existuje její modernější alternativa (funguje od PHP 5.3), ukážeme si rovnou tu.

Ve chvíli, kdy vytvoříme instanci nějaké třídy (nebo ke třídě přistupujeme staticky, viz. dále v seriálu) PHP zkontroluje, zda máme takovou třídu načtenou. V PHP je možné zaregistrovat funkci, která se spustí ve chvíli, kdy PHP zjistí, že nějakou třídu nezná. V této funkci nám od PHP přijde název nenačtené třídy a je na nás, abychom mu ji načetli pomocí require().

Přejděme na začátek index.php a definujme si zde funkci s libovolným názvem, která bere jeden parametr. Pomocí parametru sestavme cestu ke třídě a zavolejme require():

function nactiTridu($trida)
{
        require("tridy/$trida.php");
}

Nyní řekneme PHP, že má volat tuto funkci v případě, když narazí na použití nenačtené třídy:

spl_autoload_register("nactiTridu");

To je celé :) Naše require_once() pro Clovek.php a Javista.php můžeme s klidem smazat. Aplikace bude nyní fungovat stejně jako předtím a jakmile použijeme nějakou novou třídu, bude automaticky načtena. Autoloader je potřeba umístit v celé aplikaci pouze jednou, někam na začátek (klidně do indexu). O načítání tříd se již více nemusíme starat.

Zdrojový kód dnešní lekce máte jako vždy ke stažení níže a já se na vás těším příště u statických prvků.


 

Stáhnout

Staženo 580x (1.44 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

 

  Aktivity (3)

Článek pro vás napsal David Čápka
Avatar
Autor pracuje jako softwarový architekt a pedagog na projektu ITnetwork.cz (a jeho zahraničních verzích). Velmi si váží svobody podnikání v naší zemi a věří, že když se člověk neštítí práce, tak dokáže úplně cokoli.
Unicorn College Autor se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.

Jak se ti líbí článek?
Celkem (21 hlasů) :
4.952384.952384.952384.952384.95238


 


Miniatura
Předchozí článek
Dědičnost v PHP
Miniatura
Následující článek
Cvičení k 7.-8. lekci OOP v PHP

 

 

Komentáře
Zobrazit starší komentáře (24)

Avatar
Denis Homolík (Alfonz):

Toto se řeší pomocí tzv. Jmených prostorů neboli anglicky namespace. Každá třída je v jakémsi balíčku souvisejících tříd a tak by měla být pak i uložena, takže například třída Malíř ve jmeném prostoru Povolání(Povo­lani\Malir) by se měla na cházet v souboru Malir ve složce Povolani. Více o jmených prostorech by mělo být v 25. díle :)

Edit: Inoue Yūki byl rychlejší :D

Editováno 28.6.2015 15:19
Odpovědět 28.6.2015 15:19
Vše je možné, dokud si to myslíte!
Avatar
loading84
Člen
Avatar
loading84:
function nactiTridu($trida)
{
        require("tridy/$trida.php");
}


spl_autoload_register("nactiTridu");

Tenhle kod mi nefunguje a nevim proč. Ma někdo nějaký nápad. :(

 
Odpovědět 11.10.2015 21:55
Avatar
Odpovídá na loading84
Martin Konečný (pavelco1998):

OK, pro ujasnění, abychom se nemuseli jak tupci při každé otázce ptát "co na tom nefunguje?":
Když ti něco nefunguje, napiš, co přesně nefunguje (hází to tuhle a tuhle chybu / dělá to něco jiného, než by mělo / ...). V nejlepším případě sem hoď celý script, který máš.

Tak se ještě zeptám - co přesně ti na tom nefunguje?

 
Odpovědět  +1 11.10.2015 22:08
Avatar
loading84
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
loading84:

Momentálně dvě věci. Pole ze stranky: http://www.itnetwork.cz/…-datove-typy tam jsem psal zdrojaky a nově

function myAutoloader($className)
{
    $path = 'tridy/';

    include $path.$className.'.php';
}

spl_autoload_register('myAutoloader');


         function nactiTridu($trida)
        {
            require("tridy/$trida.php");
        }

        spl_autoload_register("nactiTridu");




function my_autoloader($class)

{
include 'tridy/' . $class . '.php';
}

spl_autoload_register('my_autoloader');

Ani jeden ze způsobu nefunguje. :( Tak nevim jestli nemam něco nastavné blbě. Nevím kde se mam podívat do logů. Server mi instaloval kamarad a ja se pod linuxem moc nevyznam.

 
Odpovědět 11.10.2015 22:15
Avatar
Odpovídá na loading84
Martin Konečný (pavelco1998):

V tom případě záleží, kde máš ty soubory se třídami.

Např. pokud budeš mít strukturu

index.php
tridy/Trida.php
tridy/Trida2.php
tridy/Trida3.php

tak v souboru index.php budeš mít

function myAutoloader($class)
{
        include "tridy/" . $class . ".php";
}

spl_autoload_register("myAutoloader");


$trida = new Trida();   // načte soubor  tridy/Trida.php
$trida2 = new Trida2();   // načte soubor tridy/Trida2.php

Zkus to nějak takto

 
Odpovědět 11.10.2015 22:19
Avatar
loading84
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
loading84:
function myAutoloader($class)
{
        include "tridy/" . $class . ".php";
}

spl_autoload_register("myAutoloader");


$trida = new Trida();   // načte soubor  tridy/Trida.php
$trida2 = new Trida2();   // načte soubor tridy/Trida2.php

Tak ani tohle mi nefunguje a ani tohle

<?php

require_once('tridy/pole.php');

echo '<br />';
echo '<br />';
$pole1 = new Pole([1,2,3,4,5,6]);
$pole1->zobraz();

 ?>

a třída:

class Pole
{
    /** @var array */
    private $prvky;


    public function __construct(array $prvky) // pokud to má být pouze pole, lze uvést před argumentem hint
    {
        $this->prvky = $prvky;
    }


    public function pridej($prvek)
       {
        $this->prvky[] = $prvek;
       }

    public function zobraz()
       {
       printf_r($prvky);
       }
    }

funguje mi pouze tohle

class Foo
{
    private $data = array();

    public function __construct(array $data)
    {
        $this->data = $data;
    }

    public function __get($vlastnost)
    {
        return $this->data[$vlastnost];
    }
}

$foo = new Foo(array("bar" => "baz"));
echo $foo->bar; // vypíše baz

Ja si myslím, že musím mít něco špatně nastavené v php.ini nebo tak... V obou nefunkčních případech mi vyjede prazdna stranka bez chyboveho hlašení. Prostě jenom bílo. A nevím kde se mam podívat do logu, co po mě to phpko vlastně chce a proč to nezobrazuje.

 
Odpovědět 12.10.2015 9:00
Avatar
loading84
Člen
Avatar
Odpovídá na Martin Konečný (pavelco1998)
loading84:

Tak načítaní tříd jsem vyřešil takto. Ted už mi zbývá vyřešit jenom to pole.

set_include_path(get_include_path() . PATH_SEPARATOR . 'tridy/');
spl_autoload_extensions('.php');      // alternativa spl_autoload_extensions('.php, .inc');
spl_autoload_register();
Editováno 12.10.2015 9:41
 
Odpovědět 12.10.2015 9:38
Avatar
loading84
Člen
Avatar
loading84:

Já nechápu, že nikde není postup, jak udělat objekt s parametry.

Když bude skript vypadat nějak takto.

if(podmínka1)
$select1  // vyber z databáze například uživatel
pole1=new Pole($select1)
pole1=vlozprvek();
pole1=smazprvek($parametr);
pole1=zobraz();

if(podmínka2)
$select2  // vyber z databáze například pokročilý uživatel
pole2=new Pole($select2)
pole2=vlozprvek();
pole2=smazprvek($parametr);
pole2=zobraz();

if(podmínka3)
$select3  // vyber z databáze například administrátor
pole3=new Pole($select3)
pole3=vlozprvek();
pole3=smazprvek($parametr);
pole3=zobraz();

Přece to pole nemůžu nastavovat ve třídě, to bych pak musel pro každý select dělat novou třídu. A další věc je ta, že když dam funkci smazprvek, tak mi zmizi jenom z toho pole. Nikoliv z databaze.

 
Odpovědět 12.10.2015 11:48
Avatar
loading84
Člen
Avatar
loading84:

parametry bych chtěl předávat nějak takhle

$pole = New Pole(array(a,b,c,d)); nebo
$pole1 = New Pole(array('a=>b','c=>d'));
 
Odpovědět 12.10.2015 11:55
Avatar
kuxik009
Redaktor
Avatar
kuxik009:
"Autoloader je potřeba umístit v celé aplikaci pouze jednou, někam na začátek (klidně do indexu). O načítání tříd se již více nemusíme starat."

Tohle jsem moc nepochopil, resp. se mi to zdá divné. Takže když autoloader vložím dejme tomu do indexu, a potom budu chtít načíst nějakou třídu v úplně jiném souboru, tak se o to nemusím starat i když nemá ten soubor ani třída zmínku o indexu? To by bylo kouzelné :-)

 
Odpovědět 22. března 12:47
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 10 zpráv z 34. Zobrazit vše