IT rekvalifikace s garancí práce. Seniorní programátoři vydělávají až 160 000 Kč/měsíc a rekvalifikace je prvním krokem. Zjisti, jak na to!
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

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:

Tvoje stránka
localhost

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:

Polymorfismus v JavaScriptu - Objektově orientované programování v JavaScriptu

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:

Tvoje stránka
localhost

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:

Tvoje stránka
localhost

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 132x (1.24 kB)
Aplikace je včetně zdrojových kódů v jazyce JavaScript

 

Předchozí článek
Kvíz - JSON a AJAX v JavaScriptu
Všechny články v sekci
Objektově orientované programování v JavaScriptu
Přeskočit článek
(nedoporučujeme)
Řešené úlohy k 14. lekci OOP v JavaScriptu
Článek pro vás napsal Šimon Raichl
Avatar
Uživatelské hodnocení:
162 hlasů
Autor se věnuje hlavně tvorbě všemožných věcí v JS
Aktivity