Lekce 8 - Polymorfismus, finální prvky a autoloader v PHP
V minulé lekci, Dědičnost v PHP, jsme si vysvětlili dědičnost a oddědili jsme si od člověka javistu.
Dnes se uvedeme do tzv. polymorfismusmu.
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.
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:
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(): void { 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é.
{PHP} require_once('tridy/Clovek.php'); require_once('tridy/Javista.php'); $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); foreach ($lide as $clovek) { $clovek->pozdrav(); echo('<br />'); }
{PHP} class Clovek { private int $unava = 0; public function __construct(public string $jmeno, public string $prijmeni, public int $vek) {} public function spi(int $doba): void { $this->unava -= $doba * 10; if ($this->unava < 0) $this->unava = 0; } public function behej(int $vzdalenost): void { if ($this->unava + $vzdalenost <= 20) $this->unava += $vzdalenost; else echo('Jsem příliš unavený.'); } public function pozdrav(): void { echo('Ahoj, já jsem ' . $this->jmeno); } public function __toString(): string { return $this->jmeno; } }
{PHP} class Javista extends Clovek { public function __construct(string $jmeno, string $prijmeni, int $vek, public string $ide) { parent::__construct($jmeno, $prijmeni, $vek); } public function programuj(): void { echo("Programuji v {$this->ide}..."); } public function pozdrav(): void { echo('Hello world! Jsem ' . $this->jmeno); } }
Výstup programu bude následující:
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(): void { 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 error jelikož javista se snaží z člověka dědit:
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í funkce require()
.
Přejděme na začátek souboru 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 funkci require()
:
function nactiTridu(string $trida): void { 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 funkce
require_once()
v souborech 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. Dnešní kód je ke stažení níže.
V následujícím kvízu, Kvíz - Datové typy, dědičnost a polymorfismus v PHP, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.
Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.
Stáhnout
Stažením následujícího souboru souhlasíš s licenčními podmínkami
Staženo 1080x (2.96 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP