Close

Objektově orientované programování II. – abstrakce a dědičnost

Programovací jazyky - Wordcloud

V předchozím článku ze série věnované programování jsme se dostali k úplným základům objektově orientovaného programování. Dnes budeme pokračovat a vysvětlíme si pojmy abstrakce a dědičnost.


Článek navazuje na jeho první část, kterou naleznete zde.

Abstrakce

Příkladem v předchozí části jsme si vytvořili jednoduchou abstrakci nad digitálním výstupem, která slouží k ovládání LED. Už neovládáme výstup tak, že bychom pomocí digitalWrite() přímo nastavovali HIGH a LOW na výstupu, ale ovládáme jej skrze různé metody objektu LED.

Třídu z ukázky v praxi pravděpodobně nevyužijeme, protože ovládat LED není nic složitého. Ukazuje nám ale cestu, kterou bychom se mohli vydat u složitějších programů, které ovládají pokročilejší komponenty, nebo mají komplexní chování.

Abstrakce není věcí pouze objektově orientovaného programování a setkáme se s ní i u jiných přístupů. Je to vlastně jeden z nejdůležitějších principů informatiky. Když se podíváte na vývoj programovacích jazyků, který jsem nastínil v prvním článku této série, zjistíte, že od dob strojového kódu, přes assembler, jazyk C a další, dochází k tvorbě vyšších a vyšších vrstev abstrakce. Tento pojem zde ale zmiňuji, protože je na příkladu LED velice dobře zřejmý.

Dědičnost

Jedním z významných pojmů OOP je dědičnost, kterou Wikipedia definuje následovně:

Dědičnost je v objektově orientovaném programování způsob, jak ustanovit is-a vztah mezi objekty. V třídové dědičnosti, kde jsou objekty definované třídami, mohou třídy zdědit atributy a chování od předem existujících tříd, které se nazývají rodičovské třídyzákladní třídy nebo super třídy. Výsledné třídy jsou nazývány odvozené třídy, podtřídy nebo potomek třídy. Koncept dědičnosti byl poprvé zaveden pro jazyk Simula v roce 1968.

Pojďme si tento pojem vysvětlit na příkladu. Vzpomínáte, jak jsme si v minulém článku vytvořili třídu Pes?

Přesuňme se trochu do historie – asi každý ví, že se předkem psa je Vlk, ze kterého se kdysi dávno vyvinul. Jak vypadá takový vlk? Vlk je divoké zvíře, takže rozhodně nebude mít jméno (na rozdíl od našeho psa). Vlk bude mít parametr druh, do kterého si v konstruktoru zapíšeme řetězec „Vlk“ (bude se hodit časem). Dále bude mít vlk určitě konstruktor, aby se mohl „narodit“. Řekněme, že bude umět také štěkat, ale na rozdíl od psa si při tom ještě zavyje. Vlk samozřejmě umí běhat.

class Vlk{    private:        int vyska = 0;    public:        String druh = "";        Vlk(int);        void stekej();        void behej();    }; Vlk::Vlk(int vy){    druh = "Vlk";    vyska = vy; } void Vlk::stekej(){    Serial.println("Vlk vyje Auuuuuuuu! Haf!"); } void Vlk::behej(){    Serial.print(druh);    Serial.println(" beha..."); } 

Vytvoření třídy Vlk nás už nepřekvapí. Není v ní nic, co bychom neviděli minule u třídy Pes. Tu si ale nyní trochu upravíme tak, aby dědila vlastnosti od třídy Vlk. Když porovnáme psa a vlka, pes má navíc parametr jmeno. Parametr vyska mají pes i vlk. Stejně tak funkci stekej() – ta je ale jiná, než u vlka. Určitě také bude pes umět běhat. To vše nám dědičnost umožňuje zařídit.

class Pes : public Vlk {    private:        String jmeno = "";    public:        Pes(String, int);        void stekej(); }; Pes::Pes(String jm, int vy) : Vlk(vy) {    druh = "Pes";    jmeno = jm; } void Pes::stekej(){    Serial.print(jmeno);    Serial.print(" steka ");    Serial.println("Haf!"); } 

Co se v kódu děje? Třídu začneme definovat stejně, ale za dvojtečkou následuje jméno třídy, ze které nová třída dědí, společně s klíčovým slovem public. To nám zajistí, že budeme mít ve třídě Pes přístup k public metodám a parametrům třídy Vlk. V terminologii OOP se nové třídě (Pes) říká potomek a původní třídě (Vlk) se říká rodič. Mimo modifikátor přístupu public je možné použít ještě private a protected. To už by ale bylo na tento článek moc do hloubky. Pokud by vás ale zajímaly detaily, přesměruji vás sem.

Specifikum objektu Pes je vlastnost jmeno. Dále také musíme uvést, že Pes umí štěkat.

Za definicí konstruktoru (Pes::Pes(String jm, int vy) ) následuje ještě vytvoření konstruktoru rodiče (Vlk(vy)). To nám zajistí nastavení hodnot předaných jako parametry konstruktoru do vnitřních parametrů objektu.

Mimo metody a parametry, které jsme přímo uvedli v definici objektu Pes, má objekt pes dostupné všechny public metody a parametry třídy Vlk. Možná si říkáte, proč jsem u Pes uvedl metodu stekej(), když ji má i Vlk. To je zde potřeba, protože tím programu říkáme, že pes bude mít vlastní metodu stekej() a ne stejnou, jako má vlk.

Když si nyní vytvoříme instance objektů Pes a Vlk, uvidíme, že oba mají metodu behej(), kterou jsme sice u objektu Pes explicitně nevytvořili, ale byla zděděna. Oba také mají metodu stekej(), kterou jsme pro každého upravili zvlášť.

Vlk vlk(100); Pes pes("Punta", 50); void setup() {    Serial.begin(9600); } void loop() {    vlk.behej();    vlk.stekej();    pes.behej();    pes.stekej();    Serial.println("--------------------");    delay(1000); } 

Praktický příklad

Ukažme si dědičnost opět v praxi. Asi nejjednodušší bude opět využít třídy LED diody, a to v té podobě, v jaké jsme ji minule dokončili. Máme tedy tento kód:

class LED{    private:        int pin;        boolean stav = LOW; //výchozí stav LED je vypnuto        void nastav(boolean);    public:        LED(int);        void zapni();        void vypni();        void prepni();        boolean vratStav(); }; LED::LED(int p){    pin = p;    pinMode(pin, OUTPUT);    digitalWrite(pin, stav); } void LED::zapni(){    nastav(HIGH); } void LED::vypni(){    nastav(LOW);     } void LED::prepni(){    nastav(!stav); //nastaví LED na obrácenou hodnotu (0->1, 1->0) } void LED::nastav(boolean s){    stav = s;    Serial.print("Nastavuji ");    Serial.print(stav);    Serial.print(" na pinu ");    Serial.println(pin);    digitalWrite(pin, stav); } boolean LED::vratStav(){    return stav;      } 

Vytvořme si nyní potomka LED, s názvem BlikajiciLED. To bude „konfigurovatelná ledka“, které při vytváření řekneme, s jakou periodou má blikat, a poté už ji jenom necháme blikat.

class BlikajiciLED : public LED{    private:        int perioda = 0;        unsigned long posledniZmena = 0;    public:        BlikajiciLED(int, int);        void blikej();     }; BlikajiciLED::BlikajiciLED(int pin, int _perioda) : LED(pin){    perioda = _perioda;    posledniZmena = millis(); } void BlikajiciLED::blikej(){    if(posledniZmena + perioda/2 <= millis()){        prepni();        posledniZmena = millis();    } } BlikajiciLED L9(9, 500); BlikajiciLED L10(10, 1500); void setup() {    Serial.begin(9600); } void loop() {    L9.blikej();    L10.blikej(); } 

Privátní parametr posledniZmena má v sobě uložen poslední čas změny stavu dané instance LED. Je tedy pro každý objekt unikátní, což umožňuje, aby více ledek blikalo s různou frekvencí. Také si všimněte, že nikde v kódu není použita jediná funkce delay(), tedy program nikde nečeká. Dalo by se říct, že je řízený událostmi, které jsou závislé na čase. Pokud vám není jasné, co se zde děje, podívejte se na článek Blikání bez funkce delay.


Je něco ve článku nejasné, nebo jsem něco málo vysvětlil? Zeptejte se v komentářích 🙂

Zbyšek Voda

Zbyšek Voda

Už nějaký čas se zajímám o věci kolem Internetu věcí a otevřeného hardware a software. Tak jsem se také v roce 2010 dostal k Arduinu, pro které dodnes programuji a taky píšu články o práci s ním. Baví mě vymýšlet, jak staré věci používat novým způsobem.
Zbyšek Voda

2 Comments on “Objektově orientované programování II. – abstrakce a dědičnost

Vláďa
28.7.2016 at 14:37

Dobrý den,
je možně, aby se v příkladu OBJEKTOVĚ ORIENTOVANÉ PROGRAMOVÁNÍ II.
rozblikaly diody jen funkcí např.
blikej(13,500);
blikej(12,1500); // pin, perioda
všechny informace by se vzali z této funkce včetně jména, jméno by bylo č. pinu
a nebyla by potřeba tato deklarace:
BlikajiciLED L9(9, 500);
BlikajiciLED L10(10, 1500);

Vláďa

Zbyšek Voda
Zbyšek Voda
28.7.2016 at 18:32

Dobrý den,
ano i ne.
Samotné blikání by bylo možné, ale musel byste někde mít uloženo, kdy jaká LED naposledy blikala. To je nyní uloženo v té instanci objektu, kterou deklarujeme přes blikajiciLED… 🙂

Napsat komentář