Letní akce PHP týden
Pouze tento týden sleva až 80 % na kurzy PHP. Lze kombinovat s akcí Letní slevy na prémiový obsah!
Brno? Vypsali jsme pro vás nové termíny školení Základů programování a OOP v Brně!

Lekce 7 - Dědičnost v PHP

Unicorn College Tento obsah je dostupný zdarma v rámci projektu IT lidem.
Vydávání, hosting a aktualizace umožňují jeho sponzoři.

V minulé lekci, Referenční a primitivní datové typy v PHP, jsme si vysvětlili rozdíl mezi referenčními a primitivními datovými typy. Dnes si v PHP tutoriálu opět 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 člověk a jen do ní přidat metodu programuj(). Kód by vypadal asi takto:

<?php

class Javista
{

    public $jmeno;
    public $prijmeni;
    public $vek;
    private $unava = 0;

    public function __construct($jmeno, $prijmeni, $vek)
    {
        $this->jmeno = $jmeno;
        $this->prijmeni = $prijmeni;
        $this->vek = $vek;
    }

    public function spi($doba)
    {
        $this->unava -= $doba * 10;
        if ($this->unava < 0)
            $this->unava = 0;
    }

    public function behej($vzdalenost)
    {
        if ($this->unava + $vzdalenost <= 20)
            $this->unava += $vzdalenost;
        else
            echo('Jsem příliš unavený.');
    }

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

    public function programuj()
    {
        echo('Programuji...');
    }

    public function __toString()
    {
        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()
    {
        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 index.php tak, aby byl v proměnné $jan javista a nechme ho pozdravit a programovat:

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();
class Clovek
{

    public $jmeno;
    public $prijmeni;
    public $vek;
    private $unava = 0;

    public function __construct($jmeno, $prijmeni, $vek)
    {
        $this->jmeno = $jmeno;
        $this->prijmeni = $prijmeni;
        $this->vek = $vek;
    }

    public function spi($doba)
    {
        $this->unava -= $doba * 10;
        if ($this->unava < 0)
            $this->unava = 0;
    }

    public function behej($vzdalenost)
    {
        if ($this->unava + $vzdalenost <= 20)
            $this->unava += $vzdalenost;
        else
            echo('Jsem příliš unavený.');
    }

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

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

}
class Javista extends Clovek
{

    public function programuj()
    {
        echo('Programuji...');
    }

}

Z výstupu programu vidíme, že vše funguje jak má. Jan má jak atributy a metody člověka, tak Javisty:

Your page
localhost

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 20ti 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.

Dědičnost objektů – grafická notace

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:

public $ide;

A metodu programuj() upravme tak, aby vypisovala i v čem javista programuje:

public function programuj()
{
    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($jmeno, $prijmeni, $vek, $ide)
{
    $this->ide = $ide;
    parent::__construct($jmeno, $prijmeni, $vek);
}

Nakonec upravíme index.php:

$jan = new Javista('Jan', 'Nový', 24, 'Eclipse');
class Clovek
{

    public $jmeno;
    public $prijmeni;
    public $vek;
    private $unava = 0;

    public function __construct($jmeno, $prijmeni, $vek)
    {
        $this->jmeno = $jmeno;
        $this->prijmeni = $prijmeni;
        $this->vek = $vek;
    }

    public function spi($doba)
    {
        $this->unava -= $doba * 10;
        if ($this->unava < 0)
            $this->unava = 0;
    }

    public function behej($vzdalenost)
    {
        if ($this->unava + $vzdalenost <= 20)
            $this->unava += $vzdalenost;
        else
            echo('Jsem příliš unavený.');
    }

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

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

}
class Javista extends Clovek
{
    public $ide;

    public function __construct($jmeno, $prijmeni, $vek, $ide)
    {
        $this->ide = $ide;
        parent::__construct($jmeno, $prijmeni, $vek);
    }

    public function programuj()
    {
        echo("Programuji v {$this->ide}...");
    }

}

A vyzkoušíme:

Your page
localhost

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 zů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()
{
    return $this->jmeno . ' ' . $this->prijmeni;
}

Metodu nyní nebudeme schopni zavolat v index.php (mimo třídu), ale budeme ji schopni zavolat jak ve třídě Clovek, tak ve třídě Javista. Pokračovat budeme zas v příští lekci, Polymorfismus, finální prvky a autoloader v PHP, kdy si uvedeme polymorfismus.


 

Stáhnout

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

 

 

Článek pro vás napsal David Čápka
Avatar
Jak se ti líbí článek?
31 hlasů
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 sítě se informační technologie naučil na Unicorn College - prestižní soukromé vysoké škole IT a ekonomie.
Předchozí článek
Referenční a primitivní datové typy v PHP
Všechny články v sekci
Objektově orientované programování (OOP) v PHP
Miniatura
Následující článek
Polymorfismus, finální prvky a autoloader v PHP
Aktivity (12)

 

 

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

Avatar
Marian Bubenik:7. února 23:24

Ahoj, děkuji za odpověď, tomu rozumím, ale...
"... Metodu nyní nebudeme schopni zavolat v index.php (mimo třídu), ale budeme ji schopni zavolat jak ve třídě Clovek, tak ve třídě Javista."
Jak sám píšeš: "Metodu celeJmeno() tedy můžeš využít pouze v potomcích třídy Clovek - např. ve třídě Javista."

Tedy chápu to tak, že pokud si udělám instanci třídy Clovek nebo Javista, měl bych být schopný metodu použít.
proto jsem vytvoril (podle lekce)
$jan = new Javista('Jan', 'Nový', 24, 'Eclipse');
a myslel, že metodu celeJmeno() použiju v $jan->...
echo $jan->celeJmeno();
Asi to špatně chápu. :)

 
Odpovědět 7. února 23:24
Avatar
Odpovídá na Marian Bubenik
Michal Šmahel:8. února 10:30

Ano, chápeš to špatně. Když si uděláš instanci nějaké třídy (vytvoříš objekt), již se jedná o přístup zvenčí. K tomuto přístupu potřebuješ modifikátor public (veřejný). Private a protected můžeš využít pouze v daných třídách (souborech).

Odpovědět 8. února 10:30
Nejdůležitější je motivace, ovšem musí být doprovázena činy.
Avatar
Odpovídá na Michal Šmahel
Marian Bubenik:8. února 10:36

Ok, díky, jak by tedy prakticky vypadalo použití (využití) ve třídách Clovek nebo Javista?

 
Odpovědět 8. února 10:36
Avatar
Odpovídá na Marian Bubenik
Michal Šmahel:8. února 10:56

Je to myšleno jako pomocná metoda. Třída Clovek by mohla mít metodu predstavSe(), která by této metody využívala. Javista by mohla mít metodu napisSvujCopy­right(), také by ji využila.

// Clovek
public function predstavSe(): string
{
    return "Jmenuji se " . $this->celeJmeno();
}

// Javista
public function napisSvujCopyright(int $pocatecniRok): string
{
    return "Copyright © " . parent::celeJmeno() . " {$pocatecniRok}-" . date("Y");
}

// index.php
$jarda = new Clovek(...);
echo $jarda->predstavSe();

$standa = new Javista(...);
echo $standa->napisSvujCopyright(2018);

Za syntaktické chyby se omlouvám. Původně jsem se nějak špatně orientoval a psal to v Javě a navíc to píšu na telefonu. Jde tu však o to nastínit reálné použití a na to by to mělo jistě stačit.

Editováno 8. února 10:58
Odpovědět 8. února 10:56
Nejdůležitější je motivace, ovšem musí být doprovázena činy.
Avatar
Odpovídá na Michal Šmahel
Marian Bubenik:8. února 16:19

Ale aby mi fungovalo použití metody celeJmeno(), tak stejně tato metoda musí být public... ne?

 
Odpovědět 8. února 16:19
Avatar
Odpovídá na Marian Bubenik
Michal Šmahel:8. února 16:30

Jak to myslíš? Vždyť tady metodu celeJmeno() zvenčí nepoužíváš. Použití je zapouzdřené uvnitř tříd Clovek a Javista. Můžeš si vyzkoušet podobné rozšíření sám naprogramovat.

Jestli myslíš to, že stejně dojde k volání této metody, které bude mít vlastně původ zvenčí, tak to nevadí. Je třeba se na to dívat tak, že je jedno, co ty veřejné metody dělají, to tě nezajímá, když je voláš na instanci. Zajímá tě to pouze tehdy, programuješ-li kód dané třídy. Pak již platí to, že metodu voláš (používáš) uvnitř třídy, takže může být i protected a private.

Odpovědět 8. února 16:30
Nejdůležitější je motivace, ovšem musí být doprovázena činy.
Avatar
Odpovídá na Michal Šmahel
Marian Bubenik:8. února 20:33

:) nerad bych tady zapleveloval prostor jalovými dotazy... ale dívám se na to tak, že pokud použiju uvedený příklad, funguje mi to, "rozumím tomu" do okamžiku, než např. metodu celeJmeno() nastavím jako protected nebo private a v tom okamžiku skript končí fatal error, že volám protected metodu, proto v tom nějak plavu... :( (díky za trpělivost)

 
Odpovědět 8. února 20:33
Avatar
Odpovídá na Marian Bubenik
Michal Šmahel:8. února 22:32

No, asi to tady nebudeme dále rozvádět. Jestli ti něco konkrétního pořád nefunguje, napiš mi soukromou zprávu a pošli mi dotčené zdrojové kódy (nahrát je můžeš přes zdejší systém nebo na Pastebin).

Odpovědět 8. února 22:32
Nejdůležitější je motivace, ovšem musí být doprovázena činy.
Avatar
Odpovídá na Michal Šmahel
Marian Bubenik:9. února 9:29

OK, díky, pokud zjistím, co dělám blbě, dám to sem a ozvu se...

 
Odpovědět 9. února 9:29
Avatar
Láďa
Člen
Avatar
Láďa:20. května 9:14

Ahoj, zkoušel jsem udělat potomka třídy Imagick:

class ftImagick extends Imagick {

public $file;
public $rozmer;

public function __construct($file, $rozmer,$rozmer_tmb){

//proč funguje jen tohle??
$this->file=$file;
parent::__con­struct($file);

$this->rozmer=$rozmer;
$this->rozmer_tmb=$roz­mer_tmb;
// $this->file=$file;

}

public function zmensi(){

$info = getimagesize($this->file);
$sirka = $info[0]; $vyska = $info[1];

/*
if ($sirka > $vyska) {
if ($sirka > $this->rozmer) $scle = 1;
}else if ($sirka <= $vyska) {
if ($vyska > $this->rozmer) $scle = 1;}
*/

if (($sirka > $this->rozmer)||($vyska > $this->rozmer)) $scle = 1;

if ($scle == 1) {
// pokud chces zmensit obrazek na maximalni velikost:
$this->scaleImage($this->rozmer, $this->rozmer, true);
$this->writeImage($this->file);
unset ($scle);
}
}
}

 
Odpovědět 20. května 9:14
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 35. Zobrazit vše