21. Seznámení s OOP I

Co je objekt?
Když se rozhlédneme okolo sebe vidíme mnoho příkladů objektů skutečného světa. Je to např. pes, stůl, televizor nebo jízdní kolo. Tyto objekty skutečného světa sdílejí dvě charakteristiky: všechny mají stav a všechny mají chování. Např. pes má stav (jméno, barvu, rasu) a pes má chování (štěkot, slintání apod.). Jízdní kolo má stav (dvě kola, počet převodů, rychlost šlapání) a chování (brzdění, akcelerace, změna převodu).
Objekty skutečného světa můžeme reprezentovat pomocí programových objektů. Můžeme chtít znázornit psa ze skutečného světa jako programový objekt v animačním programu nebo jízdní kolo skutečného světa jako programový objekt v elektronickém trenažéru jízdního kola. Programové objekty ale také můžeme použít k modelování abstraktních představ. Např. událost je obecný objekt používaný v systému GUI k reprezentaci akce stisknutí tlačítka myši nebo klávesy na klávesnici.
Následující obrázek je častá vizuální reprezentace programového objektu.

To že programový objekt má stav a může něco dělat (má chování) je zajištěno proměnnými (datovými složkami) a metodami v tomto objektu. Programový objekt modelující naše jízdní kolo tedy musí mít proměnné, které indikují současný stav kola, tj. jeho současnou rychlost, jeho právě zařazený převodový stupeň atd. Následující obrázek ukazuje model jízdního kola jako programový objekt.

Programové jízdní kolo má také metody k brzdění, změně převodu, změně rychlosti šlapání, atd. Jízdní kolo nemá metodu pro změnu rychlosti, neboť rychlost kola závisí na rychlosti šlapání a zařazeném převodu. Něco objekt jízdního kola ale nemá a něco nemůže provádět. Např. naše jízdní kolo (pravděpodobně) nemá jméno a nemůže štěkat. Tedy ve třídě jízdního kola nejsou proměnné nebo metody pro tento stav a chování.
Jak můžeme vidět na předchozím obrázku, proměnné objektu jsou umístěny uprostřed (v jádru) objektu. Metody obklopují a skrývají jádro objektu před ostatními objekty v programu. Zabalení proměnných objektu v jádru chráněném svými metodami se nazývá zapouzdření. Zapouzdření je obvykle použito k ukrytí implementačních detailů před jinými objekty. Když chceme změnit ?převodový stupeň, nepotřebujeme znát jak převodový mechanismus pracuje, stačí pouze přesunout páčku. Podobně v programu, nepotřebujeme znát jak třída je implementována, potřebujeme pouze vědět, kterou metodu vyvolat. Implementační detaily je tedy možno měnit bez ovlivnění ostatních částí ?,
Tento koncepční obrázek objektu, s jádrem proměnných zabalených v ochranné slupce metod, je ideální reprezentací objektu a je ideálem, který se návrháři objektově orientovaných systémů snaží dosáhnout. Nicméně to nelze vždy.
Objekty mohou chtít zveřejnit své proměnné pro další objekty a tím umožnit jiným objektům prohlížet nebo modifikovat tyto proměnné. Objekt také může chtít skrýt metody před jinými objekty a zabránit tak těmto objektům vyvolávat metody. Objekt má kompletní řízení nad tím, zda jiné objekty mohou přistupovat k proměnným a metodám objektu.



Co jsou zprávy?
Samotný objekt obecně není moc užitečný a obvykle je použit jako část větší aplikace, která obsahuje mnoho objektů. Teprve vzájemným působením těchto objektů, program získá svoji funkčnost a složité chování. Naše jízdní kolo stojící v garáži a které je nepoužívané, není moc užitečné. Jízdní kolo je užitečné pouze tehdy, když jiný objekt (např. já) na něj začne působit (např. začnu šlapat).
Programové objekty vzájemně působí na jiné objekty a komunikují s nimi pomocí zasílání zpráv. Když objekt A chce, aby objekt B provedl jednu ze svých metod, pak objekt A zasílá zprávu objektu B.


Někdy přijímající objekt vyžaduje nějaké informace, které určí co dělat. Např. když chceme změnit převod našeho jízdního kola, musíme indikovat, který převod chceme použít. Tato informace je předávána jako parametr zprávy.

Zpráva se skládá ze tří prvků:

Tyto tři prvky jsou informace pro přijímající objekt k provedení určené metody. Další informace nejsou vyžadovány.
Chování objektu je ovlůivňováno pomocí jeho metod a tedy (mimo přímého přístupu k proměnným) zprávy provádějí všechno potřebné vzájemné působení mezi objekty. Komunikující objekty nemusí být ve stejném procesu ani na stejném počítači.


Co jsou třídy?
Ve skutečném světě, často máme mnoho objektů stejného typu. Např. naše jízdní kolo je právě jedno z mnoha jízdních kol na světě. Pomocí objektově orientované terminologie, můžeme říci, že objekt našeho jízdního kola je instance třídy objektů známých jako jízdní kola. Jízdní kola mají nějaký společný stav (počet převodů nebo počet kol) a společné chování (změna převodu, brzdění). Ale stav každého jízdního kola je nezávislý a může se lišit od stavu jiných jízdních kol.
Když vyrábíme jízdní kola, pak řadu jízdních kol vytváříme podle stejného plánu (vyrobená jízdní kola mají některé stejné charakteristiky). Bylo by značně neefektivní vytvářet nový plán pro každé vyrobené jízdní kolo. V objektově orientovaném programu je také možno mít mnoho objektů stejného typu, které sdílí nějaké charakteristiky. Stejně jako u skutečných jízdních kol, můžeme přijmout skutečnost, že programové objekty stejného typu jsou si podobné a jsou vytvořeny podle programového plánu těchto objektů. Programový plán pro objekty se nazývá třída.
Např. můžeme vytvořit třídu jízdní kolo, která se skládá z několika instancí proměnných k uložení aktuální rychlosti, aktuálního převodu, atd. Třída také deklaruje a poskytuje implementace pro metody instance, které umožňují změnu převodu, brzdění apod. Třídu můžeme znázornit stejným obrázkem jako objekt.
Hodnoty pro proměnné objektu jsou poskytnuty pro každou instanci třídy. Po vytvoření třídy jízdní kolo, musíme před použitím instancí třídy tyto instance vytvořit. Když vytváříme instanci třídy, vytváříme objekt tohoto typu a systém alokuje paměť pro proměnné deklarované ve třídě. Pak již můžeme vyvolávat metody objektu a provádět s objektem různé činnosti. Instance stejné třídy sdílí implementace metod (jsou umístěny v samotné třídě).


Co je dědičnost?
Objektově orientované systémy umožňují aby třídy byly definovány pomocí jiných tříd. Např. horská kola, závodní kola a tandemy jsou různé typy jízdních kol. V objektově orientované terminologii, horská kola, závodní kola a tandemy jsou všechno podtřídy (potomci) třídy jízdní kolo. Obdobně třída jízdní kolo je nadtřída (předek) tříd horské kolo, závodní kolo a tandem.

Každá podtřída dědí stav (ve formě instancí proměnných) od nadtřídy. Horská kola, závodní kola a tandemy sdílejí nějaký stav (např. rychlost). Každá podtřída také dědí metody od nadtřídy. Horská kola, závodní kola a tandemy sdílejí nějaké chování (např. brzdění).
Podtřída ale není omezena stavem a chováním poskytnutým ji její nadtřídou. Podtřída může k zděděným proměnným a metodám přidat své vlastní proměnné a metody. Tandemy mají dvě sedla a dvojici řidítek; některá horská kola mají speciální převod s malým převodovým poměrem.
Podtřídy také mohou přepisovat zděděné metody a poskytují specializovanou implementaci těchto metod. Např. jestliže máme horské kolo se speciálním převodem, musíme změnit metodu Změna převodu, tak aby jezdec mohl použít tento nový převod.
Nejsme omezeni jen na jednu vrstvu dědění. Strom dědičnosti nebo hierarchie tříd může mít libovolný počet úrovní. Metody a proměnné se dědí přes tyto úrovně. Obecně, čím hlouběji v hierarchii tříd se třída vyskytuje, tím více specializované je její chování.
Podtřídy poskytují specializované chování na základě společných prvků poskytnutých nadtřídou. Pomocí použití dědičnosti, programátoři mohou mnohokrát opětovně používat kód v nadtřídě. Programátoři mohou implementovat nadtřídy nazývané abstraktní třídy, které definují všeobecné chování. Abstraktní nadtřídy definují a mají částečnou implementaci chování, ale většina třídy je nedefinovaná a neimplementovaná. Tyto detaily jsou doplněny ve specializovaných podtřídách.


  1. S OOP se nejprve budeme seznamovat na konzolových aplikacích. Určete co provádí následující program (nejedná se o program OOP).

  2. struct jeden_udaj {
      int hodnota;
    };
    int main(int argc, char **argv)
    {
      jeden_udaj pes1, pes2;
      int prase;
      pes1.hodnota = 12;
      pes2.hodnota = 17;
      prase = 123;
      cout << "Hodnota pes1 je " << pes1.hodnota << endl;
      cout << "Hodnota pes2 je " << pes2.hodnota << endl;
      cout << "Hodnota prasete je " << prase << endl;
      return 0;
    }
  3. Předchozí "nesmyslný" program se pokusíme převést do objektově orientovaného programování. Po tomto převodu dostaneme:

  4. class jeden_udaj {
      int hodnota;
    public:
      void nastav(int int_hodnota);
      int ziskej(void);
    };
    void jeden_udaj::nastav(int int_hodnota){
      hodnota = int_hodnota;
    }
    int jeden_udaj::ziskej(void){
      return hodnota;
    }
    int main(int argc, char **argv)
    {
      jeden_udaj pes1, pes2;
      int prase;
      pes1.nastav(12);
      pes2.nastav(17);
      prase = 123;
      cout << "Hodnota pes1 je " << pes1.ziskej() << endl;
      cout << "Hodnota pes2 je " << pes2.ziskej() << endl;
      cout << "Hodnota prasete je " << prase << endl;
      return 0;
    }
    Tento program dává stejné výsledky, jako předchozí program. Je zde ale několik změn. Namísto struktury je použita třída. Jeden z rozdílů mezi strukturou a třídou je ten, že třída začíná soukromou částí, zatímco struktura začíná veřejnou částí. Soukromá část třídy je část datových složek, která není přístupná z vnějšku třídy (je ukryta jakémukoli vnějšímu přístupu). Proměnná hodnota, která je částí objektu pes1, není dostupná z hlavního programu. Zdá se to trochu podivné deklarovat proměnnou, kterou nemůžeme použít, ale přesně to jsme udělali. Naše třída je složena z jednoduché proměnné nazvané hodnota a dvou funkcí, z nichž jedna se jmenuje nastav a druhá ziskej.
    Nové klíčové slovo public zahajuje veřejnou část (tj. veřejné rozhraní). Tato část je přístupná z hlavního programu. Obě funkce v naší třídě leží ve veřejné části a jsou tedy přístupné pro použití z hlavního programu. Proměnná hodnota je přístupná pouze ve funkcích definujících veřejné rozhraní třídy. Funkce třídy se nazývají metody (nebo členské funkce), neboť jsou částí třídy.
    Jelikož jsme deklarovali dvě funkce, musíme je také definovat, tj. určit co budou dělat. To provádíme obvyklým způsobem, s tím rozdílem, že jméno funkce předchází jméno třídy a je odděleno dvěma dvojtečkami. Tyto definice se nazývají implementace metod. Jméno třídy je požadováno, protože můžeme v několika třídách použít stejné jméno funkce a překladač musí vědět, se kterou třídou má spojit kterou implementaci. Podstatné je, že soukromá data třídy jsou dostupná v metodách třídy pro modifikování nebo čtení, ale soukromá data z jiných tříd zde dostupná nejsou.
    S použitím terminologie OPP můžeme říci, že naše třída obsahuje jednu proměnnou (datovou složku) a dvě metody. Metody operují na proměnných třídy. V deklaraci typu třídy jsou uvedeny prototypy metod.
    V programu (po skončení definicí) deklarujeme dva objekty třídy jeden_udaj a nazveme je pes1 a pes2. Každý objekt obsahuje jednu datovou složku, kterou můžeme nastavovat pomocí jedné metody a číst pomocí druhé. Nejprve zasíláme zprávu objektu pes1 s instrukcí aby nastavil vnitřní hodnotu na 12. Objekt pes1 má metodu nastav(), která nastavuje jeho vnitřní hodnotu na aktuální parametr obsažený ve zprávě. Použitý zápis se podobá přístupu k prvku struktury (jméno objektu, tečka a jméno metody). Dále obdobně zašleme zprávu druhému objektu. Druhá metoda je také definována pro všechny objekty a v příkazech cout je použita. Objektu je zaslána zpráva a vrácená hodnota je vypsána.
    V programu je také použita proměnná prase. Je to normální proměnná a používáme ji běžným způsobem.
  5. Do předchozího programu přidejte metodu vracející druhou mocninu uložené hodnoty. Do programu vložte dále několik řádků kódu pro zjištění a zobrazení druhé mocniny uložené hodnoty.
  6. Zjistěte co provádí následující konzolová aplikace:

  7. int plocha(int obd_vyska, int obd_sirka);
    struct obdelnik {
      int vyska;
      int sirka;
    };
    struct pole {
      int delka;
      int sirka;
    };
    int plocha(int obd_vyska, int obd_sirka) {                  // plocha obdélníka
      return obd_vyska * obd_sirka;
    }
    int main(int argc, char **argv)
    {
      obdelnik okno, ctverec;
      pole moje_pole;
      okno.vyska = 12;
      okno.sirka = 10;
      ctverec.vyska = ctverec.sirka = 8;
      moje_pole.delka = 50;
      moje_pole.sirka = 6;
      cout << "Plocha okna je " << plocha(okno.vyska, okno.sirka) << endl;
      cout <<"Plocha ctverce je "<<plocha(ctverec.vyska,ctverec.sirka)<<endl;
      cout <<"Nesmyslná plocha je "<<plocha(ctverec.vyska, okno.sirka)<<endl;
      cout<<"Špatná plocha je "<<plocha(ctverec.vyska,moje_pole.sirka)<<endl;
      return 0;
    }
    V tomto programu jsou poslední dva výsledky v reálném světě nesmyslné.
  8. Předchozí program převedeme do OOP a dostaneme:

  9. class obdelnik {                            // jednoduchá třída
      int vyska;
      int sirka;
    public:
      int plocha(void);                           // s dvěmi metodami
      void inicializace(int, int);
    };
    int obdelnik::plocha(void){              // plocha obdélníku
      return vyska * sirka;
    }
    void obdelnik::inicializace(int nova_vyska, int nova_sirka){
      vyska = nova_vyska;
      sirka = nova_sirka;
    }
    struct pole {
      int delka;
      int sirka;
    };
    int main(int argc, char **argv)
    {
      obdelnik okno, ctverec;
      pole moje_pole;
      okno.inicializace(12,10);
      ctverec.inicializace(8,8);
      moje_pole.delka = 50;
      moje_pole.sirka = 6;
      cout << "Plocha okna je " << okno.plocha() << endl;
      cout << "Plocha ctverce je " << ctverec.plocha() << endl;
      //cout << "Nesmyslná plocha je " <<plocha(ctverec.vyska, okno.sirka)<<endl;
      //cout << "Špatná plocha je "<<plocha(ctverec.vyska,moje_pole.sirka)<<endl;
      return 0;
    }
    V tomto programu obdelnik je změněn na třídu se dvěmi složkami, které jsou nyní soukromé a dvěmi metodami. Jedna z metod je použita k inicializaci hodnot vytvořeného objektu a druhá metoda vrací plochu objektu. Pole je ponecháno jako struktura. V programu jsou deklarovány dva objekty (okno a ctverec), ale již nelze přiřadit hodnoty přímo jejím položkám. Jejich inicializace nyní probíhá zasláním zpráv objektům. Nelze již provádět nesmyslné výpočty tak, jako v předcházejícím programu.
    S daty mohou být prováděny pouze ty operace, které jsou definovány metodami (data jsou tedy chráněna před nesmyslnými operacemi). Zapouzdření a skrývání dat spojuje data a funkce pevně dohromady a omezuje obor platnosti a viditelnost soukromých prvků třídy. 

Nové pojmy:

21. Seznámení s OOP I