Základní pojmy OOP (třída, objekt, konstruktor, metoda, vlastnosti, přístupová práva, dědičnost)

Objekt a jeho vlastnosti

Takže, co to vůbec je ten „objekt“? Objekt je (v programování) soběstačná entita, která obsahuje data a funkčnost (volně parafrázováno odsud). Fajn… a to znamená co? Představte si výrobu v nějaké firmě. Obecně bychom ji mohli popsat tak, že tam jsou různé entity (budova, zaměstnanec, stroj, …), každá z nich realizuje nějaké konkrétní funkce a celý proces výroby jsou vlastně interakce mezi těmi entitami (zaměstnanci komunikují spolu navzájem a se stroji, stroje přetvářejí materiál a komunikují se zaměstnanci, atd.) Přitom ty entity jsou do jisté míry soběstačné, můžeme třeba vyměnit stroj za jiný, přeřadit zaměstnance na jinou práci, a podobně. Podobně to je v objektově orientované aplikaci: Ty entity jsou objekty, činnost aplikace spočívá v interakci mezi nimi a objekty by měly být natolik uzavřené a soběstačné, aby stejný objekt šel použít i jinde (pokud tam potřebuji stejnou funkčnost) a naopak objekt mohl v aplikaci být nahrazen jiným (pokud ten jiný lépe vyhovuje požadavkům).

Programátorské objekty, podobně jako ty reálné, popisujeme jejich vlastnostmi (viz vtip: Proč je slon velký, šedý a vrásčitý? Protože je to slon, kdyby byl malý, bílý a hladký, byl by to Aspirin.) Ty můžeme rozdělit na dva druhy: Jaký je (v případě slona z vtipu velký, šedý, vrásčitý – v programování jsou to data) a co dělá neboli chování (slon dýchá, chodí, pije, atd. – v programování jsou to funkce). V OOP ten první druh vlastností (data) nazýváme atributy a druhý druh vlastností (funkčnost) jsou metody.

Když to srovnáme s procedurálním programováním, tam máme data (třeba proměnné jsou data), dokonce můžeme sdružovat podobná data (třeba do pole), ale nemůžeme sdružit data a funkce. Nemůžeme říct: „Tato sada dat patří k této skupině funkcí“. V tom se základní rozdíl mezi procedurálním a objektovým programováním.

Třída a instance

Třída je množina objektů s určitými vlastnostmi. Přitom samotná třída nedefinuje nějaké konkrétní objekty té třídy, jen udává, jaké vlastnosti bude mít každý objekt té třídy (podobně představa o tom co je telefon může existovat i kdyby všechny existující telefony zanikly).

Naopak instance je konkrétní objekt určité třídy. Tedy ty konkrétní telefony vyobrazené výše jsou instance telefonu. Třída může například definovat, že každý telefon má nějakou barvu (tedy třída Telefon bude mít atribut Barva). Instance pak atributu přiřadí konkrétní hodnotu, například na obrázku výše telefon vlevo je černý.

Instanční a třídní vlastnosti

Jak jsme si výše ukázali (s barvou telefonu), třída definuje, jaké vlastnosti bude mít její instance (a instance pak určí konkrétní hodnoty atributů). Abychom mohli zjistit barvu telefonu, musíme mít na mysli nějaký konkrétní telefon (instanci), obecný koncept telefonu (třída) žádnou konkrétní barvu nemá. Kromě toho se ale někdy může stát, že má nějakou vlastnost samotná třída. Řekněme, že bychom pro nějaké účely chtěli telefon reprezentovat symbolem ☎ a tuto informaci chceme promítnout do třídy. Zjevně nejde o vlastnost instance, protože symbol vyplývá z příslušnosti ke třídě a můžeme ho zjistit i kdyby neexistoval žádný konkrétní telefon (instance). Jde tedy o vlastnost třídy. Třídní vlastnosti mohou být obojího druhu, tj. atributy třídy a metody třídy. V mnoha programovacích jazycích, včetně PHP, se třídní vlastnosti nazývají statické, tedy statické atributy a statické metody.

Dědění a skládání

Skládání je celkem jednoduchý koncept: Objekty lze skládat. Abychom se odpoutali od telefonů, představme si třídu Bod (v rovině), její atributy budou souřadnice X a Y. A budeme chtít udělat třídu Kruh. Kruh je určený středem a poloměrem, přičemž střed je bod, takže atribut Střed bude objekt typu Bod.

Dědění je také intuitivní: Často se stává, že objekt patří do určité třídy, ale sama ta třída patří pod další, obecnější třídu. Například bychom měli ve firmě třídu Osoba, kdy všechny osoby mají určité společné údaje (třeba jméno a adresu), ale pak se člení na zaměstnance, dodavetele a klienty a každý typ osoby má nějaké svoje dodatečné údaje. Zde se právě uplatní dědění: Můžeme vytvořit třídu Zaměstnanec, která bude odvozená od třídy Osoba. V tom případě se třída Zaměstnanec stane specializací třídy Osoba a naopak třída Osoba je zobecněním třídy Zaměstnanec. Třída Zaměstnaneczdědí vlastnosti třídy Osoba a může k nim přidat své vlastní. Dědění není omezené na jednu úroveň, od odvozené třídy mohou být odvozené další třídy. K jedné třídě také může existovat libovolný počet odvozených tříd (které od ní dědí), ale obráceně to v PHP neplatí, jedna třída může dědit jen od jedné jiné třídy.

Můžeme si všimnout, že dědění by teoreticky šlo nahradit skládáním: Místo dědění si můžeme objekt dané třídy vložit a dosáhneme podobného výsledku. Co je vhodnější záleží na situaci a účelu.

Při návrhu dědičnosti v aplikaci je také nutné dát pozor, že některé vztahy zobecnění-specializace z reálného světa není vhodné modelovat stejným vztahem (dědičností) i v aplikaci. Například čtverec je v matematice speciální případ obdélníka. Třída Obdélník může bez problému mít dvě metody pro změnu šířky či výšky (prodlouží či zkrátí příslušné dvojice stran). Když třídu Čtverec odvodíme od třídy Obdélník, zdědí i tyto dvě metody, které ale pro čtverec nedávají smysl (u čtverce není možné změnit jen šířku nebo jen výšku tak, aby výsledek byl pořád čtverec). Popsaný problém se nazývá Problém čtverce a obdélníka či Problém kruhu a elipsy.

Viditelnost vlastností

Některé vlastnosti objektu potřebuje objekt mít kvůli svému internímu použití, ale je nežádoucí, aby je používal někdo „zvenku“ (například PIN své platební karty potřebujete znát, ale asi ho nebudete sdělovat každému na potkání). Proto lze vlastnosti rozdělit na veřejné (public), které lze volat i „zvenku“ (tzn. například vytvořím si instanci objektu a zavolám jeho metodu), a soukromé (private), které mohou používat jen jiné metody téhož objektu. Soukromé vlastnosti se navíc ani nezdědí do odvozených tříd. Proto existuje ještě třetí úroveň, chráněné (protected) vlastnosti, které se podobají soukromým, ale dědí se do odvozených tříd.

Zapouzdření

Koncept zapouzdření souvisí s výše zmíněnou viditelností a má také analogii v reálném světě: Například televizory mívají v manuálu popsané, že určité tlačítko zesílí (resp. ztlumí) zvuk. Moderní televizor nejspíš mezi stiskem tlačítka a změnou hlasitosti provede spoustu skrytých (v naší programátorské analogii by byly private, soukromých) procesů, o kterých uživatel vůbec neví. Televizor Tesla z 80. let má také tlačítka pro změnu hlasitosti, ale docílí toho nejspíš úplně jiným postupem. Uživatel přesto dokáže funkci ovládat na obou televizorech, protože nepotřebuje znát, jakým postupem televizor změnu hlasitosti provedl, stačí mu vědět, že určitý ovládací prvek zařídí změnu hlasitosti.

Analogicky fungují objekty v objektovém programování: Uživateli objektu by mělo stačit znát pouze veřejné vlastnosti objektu a jejich význam.

Například bychom měli třídu Osoba s metodou, která vrátí věk osoby. K použití metody stačí vědět, že vrátí věk osoby. Skutečná implementace může být třeba taková, že v datech osoby je rodné číslo, z něj metoda získá datum narození a podle něj určí věk. Později začneme evidovat i cizince, kteří nemají rodné číslo, ale místo něj známe přímo datum narození. Upravíme tedy strukturu třídy Osoba a implementaci metody na zjištění věku (poznámka: Místo změny původní třídy bývá vhodnější vytvořit novou odvozenou třídu). Objekt se změnil, ale při pohledu „zvenku“ je jeho použití pořád stejné. Dopady změn zůstaly díky zapouzdření izolované jen uvnitř objektu.

Význam dědičnosti

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řídy,zá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.

Podtřídy a super třídy

Podtřída je modulární, odvozená třída, která dědí jednu nebo více jazykových entit od jedné nebo více tříd (super tříd). Sémantika dědičnosti se v různých programovacích jazycích liší, ale obvykle podtřída dědí instanční proměnné a členské funkce svého rodiče. Některé jazyky podporují dědičnost i jiné konstrukce. Rodičovská třída zavádí společné rozhraní a základní funkcionalitu, kterou specializovaná podtřída může zdědit, modifikovat nebo potlačit. Reference na třídu, může odkazovat na jednu z jeho podtříd. Jaká třída je odkazována je nemožné předpovídat během kompilace. Pro volání funkcí objektů více různých tříd je použito uniformní rozhraní.

Přetěžování

Mnoho objektově orientovaných jazyků dovoluje objektu nebo třídě nahrazení funkce, která byla zděděná. Tento proces je nazýván přetěžování. Toto přináší komplikaci: kterou verzi funkce instance zděděné třídy používá – tu co je součástí vlastní třídy, nebo tu co je z rodičovské třídy? Odpověď se liší jazyk od jazyka, nějaké jazyky obsahují deklarace pro určení, že určité metody nemohou být přetíženy, některé obsahují deklarační slova pro označení přetížených metod.

Dědičnost je mechanizmus kde podtřída znovu použije kód rodičovské třídy. V základu si podtřída zachovává všechny operace rodičovské třídy.

V následujícím příkladu v jazyce Python, podtřída CubeSumComputer přepisuje metodu transform() rodičovské třídy SquareSumComputer. Podtřída znovu používá všechen kód rodičovské třídy, až na metodu transform().

Třídy, které nelze dědit

V některých programovacích jazycích může být třída prohlášena za neděditelnou přidáním modifikátorů do deklarace třídy. Například v jazyce Java klíčové slovo „final“, nebo v jazyce C# slovo „sealed“. Podobné modifikátory se v deklaraci píší před slovo „class“. Tyto třídy zakazují jejich znovupoužitelnost, což se používá převážně v případě, že se kód distribuuje jako předkompilované binární soubory a ne jako zdrojový kód.

Metody, které nemohou být přetíženy

Stejně jako třídy mohou být neděditelné, metody/funkce mohou být vyhlášeny za nepřetížitelné (nahrazeny novou funkcí stejného jména v podtřídě). Metody deklarované jako private jsou nepřetížitelné jednoduše proto, že nejsou přístupné z jiných tříd než jejich hlavní. Pro nepřetížitelné funkce (které jsou dostupné potomkům) se používají stejná klíčová slova jako u třídy (final, sealed).

Virtuální metody

Pokud je metoda v rodičovské třídě deklarovaná jako virtuální, potom její volání z rodičovské třídy bude automaticky přesměrováno na metodu potomka (pokud existuje). Některé jazyky požadují, aby byly virtuální funkce specificky deklarovány (C++), v jiných jazycích jsou všechny metody virtuální (Java).

Polymorfizmus

Polymorfismus je vlastnost programovacího jazyka, objektově orientovaného programování (OOP), která umožňuje:

  • jednomu objektu volat jednu metodu s různými parametry (ad-hoc polymorfismus);
  • objektům odvozených z různých tříd volat tutéž metodu se stejným významem v kontextu jejich třídy, často pomocí rozhraní;
  • přetěžování operátorů neboli provedení rozdílné operace v závislosti na typu operandů, overloading;
  • jedné funkci dovolit pracovat s argumenty různých typů (parametrický polymorfismus) – ne nutně ve vašem oblíbeném jazyce.

Rozhodnutí o tom, která metoda bude volána, je u polymorfismu prováděno až za běhu programu (tj. dynamicky pomocí virtuálních funkcí). Tím se odlišuje od přetěžování funkcí, kde je rozhodnutí o volání vhodné funkce provedeno již při překladu (tj. staticky).

Polymorfismus je vlastnost programovacího jazyka, speciálně v objektově orientovaném programování, která umožňuje objektům volání jedné metody se stejným jménem, ale s jinou implementací. V jiném kontextu (než OOP) se tento druh polymorfizmu nazývá podtypový, na rozdíl od parametrického polymorfizmu (ve funkcionálním programování), který odpovídá spíš generice (např. v C#) nebo šabloně v OOP (Java, C++).

Funkce, metoda nebo makro (a další syntaktické konstrukce) se dají volat s různými datovými typy. Implementace se liší podle druhu polymorfizmu.

(Podtypový) Polymorfismus dělíme na dva typy: statický a dynamický. Při statickém je v době překladu znám konkrétní typ argumentu a proto překladač vygeneruje volání konkrétní (monomorfní) funkce, která pracuje se správným typem. Dynamický p. vybírá volanou funkci za běhu (tzv. pozdní vazba, late binding), v OOP typicky pomocí tabulky virtuálních metod (TVM). Tato tabulka není programátorovi přímo přístupná a generuje ji překladač. V obou případech se kód funkcí generuje při překladu. Tzv. přetížené funkce (overloading) odpovídají statickému polymorfizmu, ale pro každý typ parametru/-ů generují samostatný kód (viz omezený polymorfizmus dále). Dynamický polymorfizmus potřebuje (v nějaké formě) informace o typech za běhu, proto má časovou a/nebo paměťovou režii. Statický polymorfizmus typy za běhu (při dobrém návrhu jazyka a implementace) v principu nepotřebuje, ale ze šablon/generik vygeneruje několik podobných funkcí.

Příklady: objektový polymorfismus (s virtuálními metodami) je dynamický, šablony nebo generika jsou statického typu. Dynamicky typované interpretované jazyky (Ruby, Python) používají také dynamický polymorfizmus.

Polymorfismus může být dvojího druhu: univerzální (parametr typu může být jakýkoliv), omezený (typ jen z určitého výčtu). Např. funkce maximum – nemůže být nad čímkoliv, jen nad datovým typem, který lze porovnat na větší, menší. Univerzální typ odpovídá parametrickému polymorfizmu, který lze realizovat jednou funkcí pro všechny typy (za daného omezení). Např. funkce pro délku spojového seznamu nezávisí na typu prvků v seznamu, protože je nevyužívá (a např. prvky jsou schované za pointrem). Omezený polymorfizmus závisí na typu a konkrétní typ si funkce předává jako dodatečný (schovaný) parametr, TVM je realizovaná tímto způsobem.

Pokud si chceme realizovat polymorfizmus (ve formě přetížení) sami, např. při variantních záznamech (union v C), např. pro dvě reprezentace komplexních čísel (pravoúhlou a polární), můžeme ve funkcích využít vnitřně přepínač switch podle tagu reprezentace.

Že je oblast populární a terminologie nejednotná, můžete zjistit porovnáním částí z dědičnosti, přetížení funkce, metoda a dalších.

Programovací jazyk Java a práce se souborem v jazyce Java, a třídy pro práci s textovým souborem

Práce se soubory vyžaduje vytvoření instance třídy FileReader. V případě souboru a.txt by instance vypadala následovně

  • FileReader vstupZn = new FileReader („a.txt“);

Tím můžeme číst po znacích pomocí metody Read()

  • int znak = vstupZn.read();

Pokud chceme číst celé řádky použijeme Buffered Reader, který poskytuje metodu readLine()

  • BuffeerefReader vstupRad = new BufferedReader (vstupZn);
  • String radek = vstupRad.reeadLine();

cyklus na čtení souboru v Java

  • String radek;
  • while ((radek = vstup.readLine()) != null) {
  • System.out.println (radek);
  • }
  • vstup.close();