Nahoru
 

Abstraktní třídy a polymorfismus

V objektovém programování hraje dědění tříd klíčovou roli. Někdy však potřebujeme vytvořit třídu, která udává, jaké metody musí mít její potomek implementované, avšak sama je nemá. Takové třídy nazýváme abstraktní.

Pojďme si nejdříve ukázat využití abstraktní třídy na příkladu. Mějme třídu Animal (anglicky zvíře). Tato třída obsahuje metodu „Move()“, která do konzole vypíše „Spliš žbluňk“, což reprezentuje zvuk pohybu. Je jasné, že každý potomek třídy Animal bude vydávat tento (nebo případně jiný) zvuk. Mějme však další metodu „Eat(food)“, která přijímá jeden parametr „jídlo“ (na snědení) a zvíře jej sní. Problém však je, že zvíře může jíst různými způsoby. Některá zvířata jedí specifickou stravu, některá jídlo skladují na zimu a některá drží dietu. My dopředu nemáme tušení, jak potomci třídy Animal (medvěd, člověk, ježek, …) tuto metodu implementují ale požadujeme po nich, aby jí nějakým způsobem měli naprogramovanou (například proto, že je potřebujeme krmit).

//Třída Animal
class Animal
{
    //Nějaká metoda Move()
    Move()
    {
        console.log("Spliš žbluňk");
    }

    //Chceme, aby všechna zvířata
    // měla vlastní implementaci Eat(food).
    Eat(food) { /*...*/ }
}

Metoda „Eat(food)“ je perfektním kandidátem pro abstraktní metodu. Ta nám říká, že třída Animal se automaticky stává abstraktní třídou a jakýkoliv potomek třídy Animal, který již nechce být abstraktní, musí obsahovat implementaci metody „Eat(food)“. Tedy pokud ze třídy Animal dědíme novou třídu Fish (anglicky ryba), musíme jí doprogramovat metodu „Eat(food)“.

Abstraktní třída je obecná šablona podtříd

//Abstraktní třída Animal
abstract class Animal
{
    //Nějaká metoda Move()
    Move()
    {
        console.log("Spliš žbluňk");
    }

    //Abstraktní metoda Eat(food)
    //Jakýkoliv potomek, který
    // již nechce být abstraktní,
    // musí tuto metodu implementovat.
    abstract Eat(food);
}
//Třída Fish dědící z třídy Animal
//POZOR: Nemůže fungovat, protože nemá
// implementované abstraktní metody
class Fish extends Animal
{
    Talk()
    {
        console.log("blub");
    }
}

//Třída Fish dědící z třídy Animal
//Správně dědí z třídy Animal a
// implementuje všechny abstraktní 
// metody, tedy lze ji instancovat
class Fish extends Animal
{
    Eat(food)
    {
        if (food == "bread")
            console.log("yum yum");
        else
            console.log("bleh");
    }
}

//Třída Hamster dědící z třídy Animal
//Správně dědí z třídy Animal a
// implementuje všechny abstraktní 
// metody, tedy lze ji instancovat
class Hamster extends Animal
{
    foodStorage = [];

    Eat(food)
    {
        //Přidá jídlo do úschovného pole
        this.foodStorage.push(food);
    }
}

Abstraktní třídy nelze instancovat. Není možné pracovat s třídou, které chybí implementace nějakých metod. Víme tedy, že se nikdy nemůžeme setkat s instancí abstraktní třídy.

//Nemůže fungovat, metoda Eat(food)
// nemá implementaci.
var foo = new Animal();

//Funguje bez problému
var fooo = new Fish();

K čemu nám to všechno je? Abstraktní třídy jsou perfektním nástrojem k polymorfismu. Navažme na předchozí příklad a vytvořme „normální“ třídu Farm (anglicky farma). Na farmách se zvířata musí krmit. Mějte metodu „FeedAll()“, která nakrmí všechna zvířata. Normálně bychom museli všechna různá zvířata složitě ukládat do odlišných kontejnerů, avšak nyní je lze uložit do jednoho pole s datovým typem Animal (protože z něho zvířata dědí). V metodě „FeedAll()“ místo hromady podmínek pro každé zvíře uděláme smyčku iterující přes všechna zvířata volající jejich metodu „Eat(food)“. Tato metoda je sice abstraktní, ale my víme, že zvíře schovávající se pod tímto nainstancovaným objektem je nějaké konkrétní (medvěd, člověk, …) a metodu má definovanou.

class Farm
{
    //Pole zvířat - nějaká ryba a nějaký křeček
    animals = [new Fish(), new Hamster()];

    FeedAll()
    {
        //Smyčka iterující přes všechna zvířata
        for (var i = 0; i < this.animals.length; i++)
        {
            //Nakrm zvíře, ať je jakékoliv
            this.animals[i].Eat("bread");
        }
    }
}

Abstraktní třídy jsou mocnou pomůckou hlavně pro polymorfní práce, ale využití mají samozřejmě i jinde. Jedná se o jednoduchý princip: Máme abstraktní třídu, kterou nelze instancovat a její dědící potomci musí naimplementovat všechny abstraktní metody, aby se zbavili abstraktivity (a tím pádem šly instancovat). Umožní nám to posléze seskupovat určitě třídy na základě jejich abstraktního rodiče, protože mají společné rozhraní, pouze s rozdílnou implementací (polymorfismus).