NOVINKA: Staň se datovým analytikem od 0 Kč a získej jistotu práce, lepší plat a nové kariérní možnosti. 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 v JavaScriptu

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 {
    jmeno;
    #vek;

    constructor(jmeno, vek) {
        this.jmeno = jmeno;
        this.#vek = vek;
    }

    predstavSe() {
        return `Jmenuji se ${this.jmeno} a je mi ${this.#vek} let.`;
    }
}

Kód by nás neměl ničím překvapit. Všimněme si akorát, že vlastnost pro věk jsem definovali jako privátní, aby nebyla přístupná zvenčí.

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říklad takový programátor je také člověk se jménem, věkem a schopností se představit. Navíc by ale měl umět programovací jazyk, ve kterém programuje.

Naivní řešení

Možná by ná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říklad také metodu programuj():

class Programator {
    jmeno;
    #vek;
    jazyk;

    constructor(jmeno, vek, jazyk) {
        this.jmeno = jmeno;
        this.#vek = vek;
        this.jazyk = jazyk;
    }

    predstavSe() {
        return `Jmenuji se ${this.jmeno} a je mi ${this.#vek} let.`;
    }

    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 neopakuje se). Více o něm najdeme v článku Best practices pro vývoj softwaru - Základní praktiky Představme si, že se v budoucnu rozhodneme třídu Clovek upravit a zapomeneme, že je rozkopírovaná do několika dalších souborů. Takové chyby se poté špatně hledají a samozřejmě způsobí nefunkčnost 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. 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 {
    jazyk;

    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. Dva z těchto parametrů předáváme při zavolání super() konstruktoru třídy Clovek, tedy jméno a věk. Dále zde máme již zmiňovanou vlastnost jazyk, do které zde přiřazujeme 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 novou 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. 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ě.

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říklad v C++. Potomek samozřejmě může mít dalšího potomka a tak dále.

Polymorfismus v JavaScriptu

Nenechme se vystrašit 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:

Tvoje stránka
localhost

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říklad, že použije pozdrav "Hello world!":

class Programator extends Clovek {
    jazyk;

    constructor(jmeno, vek, jazyk) {
        super(jmeno, vek);
        this.jazyk = jazyk;
    }

    programuj() {
        return `Programuji v ${this.jazyk}...`;
    }

    predstavSe() {
        return `Hello world! Jmenuji se ${this.jmeno}.`;
    }
}

Výsledek:

Tvoje stránka
localhost

Pokud metodu predstavSe() zavoláme na instanci třídy Clovek, vypíše původní text pozdravu. Získali jsme tedy shodné rozhraní a jinou funkcionalitu podle konkrétního objektu.

Problém s privátními vlastnostmi

V přepsané metodě predstavSe() záměrně nevypisujeme věk programátora a nepřistupujeme tak k vlastnosti #vek. Privátní vlastnosti a metody třídy Clovek totiž nejsou v jejím potomkovi Programator přístupné. Privátní vlastnosti a metody jsou chápány jako speciální logika konkrétní třídy, která je potomkovi utajena, i když ji vlastně používá, a nemůže ji měnit.

Abychom mohli vlastnost #vek použít ve třídě Programator, musíme ji ve třídě Clovek označit jako veřejnou. Tím ji ale učiníme přístupnou odkudkoliv a přijdeme tak o výhody zapouzdření. Aby bylo alespoň z názvu vlastnosti jasné, že se k ní zvenčí nemá přistupovat, pojmenujeme ji s podtržítkem _ na začátku, tedy _vek:

class Clovek {
    jmeno;
    _vek;

    constructor(jmeno, vek) {
        this.jmeno = jmeno;
        this._vek = vek;
    }

    predstavSe() {
        return `Jmenuji se ${this.jmeno} a je mi ${this._vek} let.`;
    }
}

Důležité je pamatovat na to, že tento způsob pojmenování vlastností a metod je pouhou konvencí. Pokud se pokusíme přistoupit k vlastnosti _vek mimo třídu Clovek nebo Programator, JavaScript nevyvolá žádnou chybu.

Ve třídě Programator nyní můžeme přistoupit k vlastnosti pro věk a vypsat tak celé představení:

class Programator extends Clovek {
    jazyk;

    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} let.`;
    }
}

Výsledek:

Tvoje stránka
localhost

V jiných jazycích, například v Javě nebo C#, se vlastnosti a metody, které jsou přístupné pouze ve třídě a v jejich potomcích, označují jako protected.

Vylepšení

Všimněme 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 {
    jazyk;

    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 Cloveka 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 175x (2.53 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í:
234 hlasů
Autor se věnuje především vývoji v JavaScriptu
Aktivity