This article has been localized into Czech by the community.
Dědičnost
V objektově orientovaném programování je dědičnost velmi důležitým konceptem. Umožňuje vám vytvářet třídu na základě jiné třídy a tím rozšiřovat a/nebo modifikovat funkčnost, aniž byste měnili základní třídu.
To dává skvělý smysl, když se podíváte na skutečný svět, kde uvidíte spoustu věcí, které jsou v podstatě jen mírně odlišnou verzí něčeho jiného. Například pes a kočka jsou považovány za dvě velmi odlišné věci, ale obě jsou zvířata a obě mají čtyři nohy a ocas. Dalším příkladem, možná více souvisejícím s programováním a webovými/softwarovými aplikacemi obecně, je uživatel a administrátor. Pravděpodobně budou moci interagovat se systémem různými způsoby, ale pravděpodobně oba mají jméno a e-mailovou adresu.
Dědičnost v JavaScriptu je snadná a velmi flexibilní. V tomto článku se do tohoto mocného konceptu ponoříme hlouběji.
Zvířata
Doposud jsme v tomto tutoriálu používali třídu Dog (Pes) jako příklad mnohokrát. Pokud bychom ale chtěli vytvořit systém, který by zvládal více zvířat než jen psy, dávalo by smysl mít třídu Animal (Zvíře) a poté nechat naši třídu Dog ji rozšiřovat. Nejprve tedy vytvořme třídu Animal:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm sort of animal and I have " + this.numberOfLegs + " legs...";
}
}
let animal = new Animal(4);
alert(animal.Describe());
Toto je nyní naše základní třída pro manipulaci se zvířaty. Jak vidíte, je to velmi jednoduché - ve skutečnosti má pouze pole pro určení počtu nohou tohoto zvířete (ve výchozím nastavení 0) a metodu pro popis zvířete. Tuto třídu můžeme samozřejmě instancovat a používat tyto funkce, stejně jako jakoukoli jinou třídu. Ale nyní se pokusme ji rozšířit:
class Dog extends Animal
{
}
let dog = new Dog(4);
alert(dog.Describe());
Všimněte si, jak je to skvělé - nyní máme třídu Dog (Pes), ale ve výchozím nastavení nemusíme na ní pracovat, protože automaticky dědí všechna pole a metody základní třídy (Animal/Zvíře) a tím pádem i veškerou její funkcionalitu.
Přepsání metod
Samozřejmě by dávalo smysl třídu Dog (Pes) trochu upravit. Můžeme to udělat přepsáním polí a/nebo metod, aby třída Dog získala více unikátních vlastností, zatímco stále dědí výchozí chování od třídy Animal (Zvíře). Dovolte mi vám ukázat, jak to můžeme udělat:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm sort of animal and I have " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
constructor()
{
super(4);
}
Describe()
{
return "I'm a dog and I have " + this.numberOfLegs + " legs...";
}
}
let dog = new Dog();
alert(dog.Describe());
Zde dělám pár zajímavých věcí. Především si všimněte, že jsem vytvořil nový konstruktor bez parametru "numberOfLegs" (počet nohou). V našem systému mají psi vždy 4 nohy, takže nemusíme toto specifikovat pokaždé, když chceme instancovat Dog (Pes). Místo toho naše třída Dog jednoduše volá základní konstruktor (použitím klíčového slova super) na třídě Animal (Zvíře), předávajíc hodnotu 4 pro parametr numberOfLegs.
Také jsem přepsal metodu Describe() (Popis), aby byla méně obecná - nyní říká, že jde o psa a ne jen o nějaké zvíře.
Klíčové slovo super
Chci vám říct trochu více o klíčovém slovu super, protože je velmi důležité, pokud jde o dědičnost. Jak vidíte z příkladu výše, může být použito k volání konstruktoru na základní třídě, což nám umožnilo zjednodušit konstruktor třídy Dog (Pes), zatímco jsme si zachovali funkcionalitu konstruktoru na třídě Animal (Zvíře).
Ale samozřejmě to funguje nejen pro konstruktory - funguje to pro všechny typy metod. To nám opět umožní upravit chování, které dědíme od základní třídy, přičemž si zachováme funkcionalitu. V našem příkladu bychom mohli upravit metody Describe() (Popis) tak, abychom mohli znovu použít většinu funkcionalit z metody Describe() na třídě Animal (Zvíře), například takto:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm an animal with " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
constructor()
{
super(4);
}
Describe()
{
return super.Describe() + " Woof woof!";
}
}
let dog = new Dog();
// I'm an animal with 4 legs... Woof woof!
alert(dog.Describe());
Nyní znovu využívám funkcionalitu metody Describe() (Popis), která je na třídě Animal (Zvíře), zatímco k ní přidávám, abych udělal chování metody Describe() jedinečné pro třídu Dog (Pes), aniž bych musel přepisovat existující kód.
To je skvělý příklad síly klíčového slova super, a jak vidíte, existuje několik způsobů, jak jej použít:
- super() - zavolá konstruktor na základní třídě
- super.metoda() - zavolá určenou metodu na základní třídě
- super.pole - odkáže na určené pole na základní třídě
Přepsání polí
Jak metody, tak pole lze přepsat - jedná se jednoduše o přidání členu se stejným názvem do dceřiné třídy. Při tomto postupu je chování nyní specifické pro dceřinou třídu, ale je stále viditelné pro rodičovskou třídu. Rád bych vám to dokázal s následujícím příkladem, kde přepíšeme pole numberOfLegs místo použití dříve ukázaného triku s konstruktorem, abychom vždy vytvářeli psy se 4 nohami:
class Animal
{
numberOfLegs = 0;
constructor(numberOfLegs)
{
this.numberOfLegs = numberOfLegs;
}
Describe()
{
return "I'm an animal with " + this.numberOfLegs + " legs...";
}
}
class Dog extends Animal
{
numberOfLegs = 4;
Describe()
{
return super.Describe() + " Woof woof!";
}
}
let dog = new Dog();
// I'm an animal with 4 legs... Woof woof!
alert(dog.Describe());
Nyní naše třída Dog (Pes) jednoduše přepisuje pole numberOfLegs (počet nohou), aby měla vždy 4 nohy, ale všimněte si, že když voláme metodu super.Describe() (super.Popis), která toto pole používá, hodnota přepsaného pole numberOfLegs je samozřejmě zde také reflektována.
Rozšiřování stávajících tříd/objektů
Další opravdu skvělou věcí, kterou můžete udělat s klíčovým slovem extends (rozšířit), je rozšíření funkčnosti vestavěných tříd/objektů. Vzhledem k tomu, že JavaScript obsahuje některé velmi užitečné vestavěné objekty, jak je popsáno jinde v tomto tutoriálu, umožní vám to stavět na těchto stávajících objektech a přidat potřebnou funkcionalitu, zatímco znovu využijete stávající funkcionalitu.
Jako příklad toho vytvořme vlastní třídu pole, která rozšíří vestavěný objekt Array (Pole), abychom přidali několik metod. Pro zábavu to nazveme AwesomeArray (ÚžasnéPole), a bude mít několik zčásti užitečných metod:
class AwesomeArray extends Array
{
isEmpty()
{
return this.length <= 0;
}
hasCat()
{
return this.includes("cat");
}
}
let array = new AwesomeArray();
// true
alert("Empty: " + array.isEmpty());
array.push("dog");
// false
alert("Has cat: " + array.hasCat());
array.push("cat");
// true
alert("Has cat: " + array.hasCat());
// dog,cat
alert(array);
array.sort();
// cat,dog
alert(array);
Jak vidíte, rozšíření vestavěné třídy/objektu je stejné jako rozšíření vašich vlastních tříd - syntaxe je identická. Poté přidáme několik velmi jednoduchých a poněkud hloupých metod, abychom zkontrolovali, zda je pole prázdné a zda obsahuje nějaké kočky.
Jak vidíte, použitím klíčového slova this jsme schopni použít funkcionalitu, která je již na objektu Array (Pole) k dispozici, jako je vlastnost length (délka) a metoda includes() (obsahuje).
Po deklaraci naší třídy AwesomeArray (ÚžasnéPole) uvidíte, jak ji můžeme instancovat jako jakoukoli jinou třídu a používat naše vlastní, uživatelské metody z ní, stejně jako metody zděděné od objektu Array (Pole), jako jsou push() (přidat) a sort() (seřadit).
Shrnutí
Dědičnost vám umožňuje stavět na vašich vlastních třídách nebo vestavěných třídách/objektech a rozšiřovat funkcionalitu, aniž byste museli měnit stávající třídu/objekt. Je to jeden z nejzákladnějších a nejdůležitějších aspektů objektově orientovaného programování, a jakmile začnete s třídami pracovat intenzivněji, zjistíte, že je to neuvěřitelně užitečné.