Lekce 7 - Dědičnost v PHP
V předešlém cvičení, Řešené úlohy k 4.-6. lekci OOP v PHP, jsme si procvičili nabyté zkušenosti z předchozích lekcí.
Dnes si v PHP tutoriálu rozšíříme znalosti o objektově orientovaném
programování. V úvodním dílu do OOP jsme si říkali, že OOP stojí na
třech základních pilířích: zapouzdření,
dědičnosti a polymorfismu. Zapouzdření a
používání modifikátoru private nám je již dobře známé.
Dnes se podíváme na dědičnost.
Dědičnost
Dědičnost je jedna ze základních technik OOP a slouží k tvoření nových datových struktur na základě starých. Vyzkoušíme si to opět na našem příkladu s lidmi.
Pokud si dobře vzpomínáte, všichni naši lidé umí pozdravit, spát a
běhat. My budeme ale chtít do naší aplikace přidat javistu (pardon, ale
PHPista zní divně
– když
mi napíšete jak se programátorům v tomto jazyce říká, změním to),
který bude umět to samé, jako člověk, ale navíc bude umět i
programovat.
Přidejte si do složky tridy/ novou třídu
Javista.php. Mohli bychom samozřejmě zkopírovat třídu
Clovek a jen do ní přidat metodu programuj(). Kód
by vypadal asi takto:
<?php class Javista { 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 programuj(): void { echo('Programuji...'); } public function __toString(): string { return $this->jmeno; } }
Kód je velmi redundantní a když budeme chtít něco změnit v člověku, musíme to změnit i zde a v dalších podobných třídách. Asi uznáte, že toto není vhodný způsob jak aplikaci rozšiřovat.
Upravme nyní třídu tak, aby používala dědičnost:
<?php class Javista extends Clovek { public function programuj(): void { echo('Programuji...'); } }
PHP nyní třídu Javista oddědí od třídy
Clovek. Toho jsme docílili pomocí klíčového slova
extends. To znamená, že programátor bude obsahovat všechny
metody a všechny atributy, které má třída Clovek plus navíc
to, co přidáme do jeho třídy.
Navíc vidíme ve třídě metodu programuj(). Modifikujme kód
v souboru index.php tak, aby byl v proměnné $jan
javista a nechme ho pozdravit a programovat:
{PHP} require_once('tridy/Clovek.php'); require_once('tridy/Javista.php'); $karel = new Clovek('Karel', 'Novák', 30); $jan = new Javista('Jan', 'Nový', 24); $karel->behej(10); $karel->behej(10); $karel->spi(1); $karel->behej(10); $jan->pozdrav(); echo('<br />'); $jan->programuj(); {/PHP}{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}{PHP} class Javista extends Clovek { public function programuj(): void { echo('Programuji...'); } } {/PHP}
Z výstupu programu vidíme, že vše funguje jak má. Jan má jak atributy a metody člověka, tak javisty:
Dědičnost naleznete v anglické literatuře pod pojmem inheritance.
Výhody dědičnosti
Výhody dědění jsou jasné, nemusíme opisovat oběma třídám ty samé atributy, ale stačí dopsat jen to, v čem se liší. Zbytek se podědí. Přínos je obrovský, můžeme rozšiřovat existující komponenty o nové metody a tím je znovu využívat. Nemusíme psát spousty redundantního (duplikovaného) kódu. A hlavně - když změníme jediný atribut v mateřské třídě, automaticky se tato změna všude podědí. Nedojde tedy k tomu, že bychom to museli měnit ručně u 20 tříd a někde na to zapomněli a způsobili chybu. Jsme lidé a chybovat budeme vždy, musíme tedy používat takové programátorské postupy, abychom měli možností chybovat co nejméně.
O mateřské třídě se někdy hovoří jako o předkovi (zde
Clovek) a o třídě, která z ní dědí, jako o potomkovi (zde
Javista). Potomek může přidávat nové metody nebo si
uzpůsobovat metody z mateřské třídy (viz dále). Můžete se setkat i s
pojmy nadtřída a podtřída.
V objektovém modelování se dědičnost znázorňuje graficky jako prázdná šipka směřující k předkovi.

Jazyky, které dědičnost podporují, buď umí dědičnost jednoduchou, kde třída dědí jen z jedné třídy, nebo vícenásobnou, kde třída dědí hned z několika tříd najednou. Vícenásobná dědičnost se v praxi příliš neosvědčila, časem si řekneme proč a ukážeme si i jak ji obejít. PHP podporuje pouze jednoduchou dědičnost, s vícenásobnou dědičností se můžete setkat např. v C++. Potomek samozřejmě může mít dalšího potomka a tak dále.
Dědičnost a konstruktory
Přidejme našemu javistovi navíc nějaký atribut. Bude se jednat např. o IDE (editor zdrojových kódů), které k programování používá.
Přidejme třídě Javista veřejný atribut $ide a
metodu programuj() upravme tak, aby vypisovala i v čem javista
programuje:
public function programuj(): void { echo("Programuji v {$this->ide}..."); }
Volání konstruktoru předka
Samozřejmě se nám bude hodit nastavit atribut $ide v
konstruktoru javisty. PHP nám konstruktor zdědilo z předka. Pokud
potřebujeme jiný, musíme si ho znovu definovat.
Definujme si tedy svůj vlastní. Měl by obsahovat vše co je potřeba k
vytvoření předka + naše nové atributy. Abychom nemuseli nastavovat všechny
atributy jako jsme to dělali v předkovi, jednoduše nastavíme jen atributy
javisty a na zbytek zavoláme konstruktor předka. To uděláme pomocí
proměnné parent. Operátoru čtyřtečky :: se
nelekejte, vysvětlíme si ho podrobněji dále v seriálu:
public function __construct(string $jmeno, string $prijmeni, int $vek, public string $ide) { parent::__construct($jmeno, $prijmeni, $vek); }
Nakonec upravíme index.php:
{PHP} require_once('tridy/Clovek.php'); require_once('tridy/Javista.php'); $karel = new Clovek('Karel', 'Novák', 30); $jan = new Javista('Jan', 'Nový', 24, 'Eclipse'); $karel->behej(10); $karel->behej(10); $karel->spi(1); $karel->behej(10); $jan->pozdrav(); echo('<br />'); $jan->programuj(); {/PHP}{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}{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}..."); } } {/PHP}
A vyzkoušíme:
Modifikátor protected
V příkladu výše nebudou v potomkovi přístupné privátní atributy, ale
pouze atributy a metody s modifikátorem public.
private atributy a metody jsou chápány jako speciální logika
konkrétní třídy, která je potomkovi utajena, i když ji vlastně
používá, nemůže ji měnit. Můžete si vyzkoušet, že ačkoli javista umí
spát, nejsme schopni uvnitř jeho třídy nijak zasahovat do atributu
$unava. V tomto případě je to tak správně, protože všichni
lidé budou spát stejným způsobem a není důvod, aby do tohoto způsobu
potomci lidí zasahovali.
Pokud bychom chtěli nějaký atribut (nebo metodu) nepřístupný zvenčí,
ale zároveň přístupný v potomcích, použijeme modifikátor přístupu
protected. Vytvořme si pomocnou metodu ve třídě
Clovek, která nám vrátí jeho celé jméno (tedy
$jmeno + $prijmeni). Metodu budeme chtít používat
jen pro vnitřní účely třídy a všech tříd, které z ní dědí.
Označíme ji tedy jako protected:
protected function celeJmeno(): string { return $this->jmeno . ' ' . $this->prijmeni; }
Metodu nyní nebudeme schopni zavolat v souboru index.php (mimo
třídu), ale budeme ji schopni zavolat jak ve třídě Clovek, tak
ve třídě Javista.
Shrnutí lekce
Dědičnost umožňuje vytvořit novou třídu podle už existující
třídy. Potomek zdědí atributy a metody předka a může k nim přidat
vlastní funkcionalitu. Díky tomu není nutné opisovat stejný kód do více
tříd a úpravy společného chování stačí provést na jednom místě. V
PHP se dědičnost zapisuje klíčovým slovem extends a
konstruktor předka lze zavolat zápisem parent::__construct().
Modifikátor protected se používá pro členy, které mají být
dostupné v dané třídě i v jejích potomcích, ale nemají být přístupné
zvenčí programu.
V příští lekci, Polymorfismus, finální prvky a autoloader v PHP, si uvedeme polymorfismus, přepisování metod a autoloader.
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 856x (2.79 kB)
Aplikace je včetně zdrojových kódů v jazyce PHP

David se informační technologie naučil na