Lekce 14 - Dědičnost a polymorfismus v JavaScriptu
V předchozím kvízu, Kvíz - JSON a AJAX v JavaScriptu, jsme si ověřili nabyté zkušenosti z předchozích lekcí.
Dnes se v tutoriálu OOP v JavaScriptu podíváme na dědičnost a polymorfismus.
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. Pojďme si to ukázat na
příkladu. Vytvoříme si obecnou třídu Clovek
, které
přiřadíme vlastnosti jméno a věk a ještě metodu na představení:
class Clovek { constructor(jmeno, vek) { this.jmeno = jmeno; this.vek = vek; } predstavSe() { return `Jmenuji se ${this.jmeno} a je mi ${this.vek}.`; } }
Kód by vás neměl ničím překvapit.
Rozšíření třídy
Řekněme, že si s touto třídou v aplikaci chvíli vystačíme. Co když pak ale potřebujeme lidi, kteří budou mít nějaké vlastnosti navíc? Např. takový programátor je také člověk se jménem, věkem a schopností se představit, ale navíc by měl programovací jazyk, ve kterém programuje.
Naivní řešení
Možná by vás napadlo vytvořit třídu Programator
,
zkopírovat do ní kód z třídy Clovek
a přidat jen navíc
vlastnost jazyk
a např. také metodu programuj()
:
class Programator { constructor(jmeno, vek, jazyk) { this.jmeno = jmeno; this.vek = vek; this.jazyk = jazyk; } predstavSe() { return `Jmenuji se ${this.jmeno} a je mi ${this.vek}.`; } programuj() { return `Programuji v ${this.jazyk}...`; } }
Toto řešení je sice funkční, ale porušuje jednu z nejzákladnějších
dobrých praktik všech programátorů - Princip DRY (Don't Repeat Yourself,
tedy neopakujte se). Představte si, že se v budoucnu rozhodnete třídu
Clovek
upravit a zapomenete, že je rozkopírovaná do několika
dalších souborů. Takové chyby se poté špatně hledají a samozřejmě
způsobí znefunkčnění vaší aplikace. Takto tedy určitě ne
Řešení pomocí dědičnosti
Novou třídu Programator
opravdu vytvoříme, ale z třídy
Clovek
ji oddědíme. V JavaScriptu se pro
dědění používá v definici nové třídy klíčové slovo
extends
, takto:
class Potomek extends Predek
Konstruktory a dědičnost
Je tu ovšem malý háček. Jakmile potřebujeme ve třídě potomka
konstruktor, musíme v něm nejprve zavolat konstruktor předka pomocí funkce
super()
, případně mu předat potřebné parametry. Toto
volání musíme provést před použitím klíčového slova this
.
Ono je to i logické, konstruktor objekt připravuje na použití a pokud má
předek nějaký konstruktor, potomek by jej měl ve svém konstruktoru zavolat,
aby vše inicializoval. Konstruktor předka musíme zavolat v konstruktoru
potomka i v případě, kdyby předek neměl definovaný svůj vlastní
konstruktor (i v tomto případě totiž konstruktor má, jen se mu vygeneroval
automaticky).
Pojďme tedy od naší třídy Clovek
oddědit novou třídu,
Programator
:
class Programator extends Clovek { constructor(jmeno, vek, jazyk) { super(jmeno, vek); this.jazyk = jazyk; } programuj() { return `Programuji v ${this.jazyk}...`; } }
Jak vidíme, máme zde konstruktor se třemi parametry, z čehož dva
předáváme při zavolání super()
konstruktoru třídy
Clovek
, tj. jméno a věk. Dále zde máme již zmiňovanou
vlastnost jazyk
, kterou zde vytvoříme v konstruktoru pro
programátora a přiřadíme do ní hodnotu ze stejnojmenného parametru.
Třída programátor má tedy nyní tři vlastnosti: jmeno
,
vek
a jazyk
a dvě metody – odděděnou
predstavSe()
a programuj()
.
Nyní si v obsluze vytvoříme instanci třídy Programator
a
vyzkoušíme si zavolat obě metody:
const programator = new Programator("Šimon", 19, "JS"); document.write(` ${programator.predstavSe()}<br> ${programator.programuj()}<br> `);
Nyní se můžeme podívat na výsledek:
Výhody dědičnosti
Výhody dědění jsou jasné, nemusíme opisovat oběma třídám ty samé vlastnosti, 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ě.
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. JavaScript 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.
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.
Představení se
Člověk má definovanou metodu predstavSe()
, která pozdraví
tímto způsobem:
Jmenuji se Marek a je mi 26.
Přepsání metody
Nyní aplikujeme polymorfismus tak, že ve třídě Programator
metodu predstavSe()
z předka upravíme. Přepsání metody v
JavaScriptu je jednoduché – stačí ji znovu definovat. Naučme tedy
programátora představovat se nějakým programátorským způsobem, např. že
použije pozdrav "Hello world!":
class Programator extends Clovek { constructor(jmeno, vek, jazyk) { super(jmeno, vek); this.jazyk = jazyk; } programuj() { return `Programuji v ${this.jazyk}...`; } predstavSe() { return `Hello world! Jmenuji se ${this.jmeno} a je mi ${this.vek}.`; } }
Výsledek:
Pokud metodu predstavSe()
zavoláte na instanci třídy
Clovek
, vypíše původní text pozdravu. Můžete si to zkusit
Získali jsme tedy shodné
rozhraní a jinou funkcionalitu podle konkrétního objektu.
Vylepšení
Všimněte si, že jsme použili základní představení z třídy
Clovek
, jen jsme před něj přidali text "Hello world!". Naše
řešení výše není ideální, pro více typů lidí bychom museli stále
kopírovat do každé třídy ten samý výchozí pozdrav a před něj teprve
dávat novou hlášku. A již víme, že podle DRY je jakékoli
kopírování stejného kódu špatně.
Základní představení může vracet metoda predstavSe()
třídy Clovek
a my ji můžeme zavolat z potomků stejně jako
předtím konstruktor člověka pomocí klíčového slova super
.
Její výstup pak jednoduše přidáme k textovému řetězci, který se bude
vracet:
class Programator extends Clovek { constructor(jmeno, vek, jazyk) { super(jmeno, vek); this.jazyk = jazyk; } programuj() { return `Programuji v ${this.jazyk}...`; } predstavSe() { return `Hello world! ${super.predstavSe()}`; } }
Zkusme si vytvořit v obsluze instanci třídy člověk a zavolejme metodu
predstavSe()
, ať vidíme rozdíl:
const programator = new Programator("Šimon", 19, "JS"); document.write(` ${programator.predstavSe()}<br> ${programator.programuj()}<br> `); const clovek = new Clovek("Marek", 26); document.write(clovek.predstavSe());
Výpis bude vypadat takto:
Tak to je pro dnešek vše
Dole jsou opět k dispozici zdrojové kódy ke stažení.
V následujícím cvičení, Řešené úlohy k 14. lekci OOP v JavaScriptu, si procvičí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 167x (1.24 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript