10. Základní pojmy z OOP
Tato kapitola je určena těm, kteří nemají zkušenosti se žádným objektovým
jazykem a stručně vysvětluje základní pojmy z objektově orientovaného
programování (OOP). Ostatním postačí, když se seznámí
s kap. 10.5..
10.1. Objekt
Každý větší program se skládá z několika modulů - jeden se stará o výstup na
obrazovku a zpracovává pokyny od uživatele, další provádí výpočty, ještě jiný
posílá data po síti atd. Každý tento modul je naprogramován do značné míry
samostatně, nejen proto, aby se v něm autor po půl roce sám vyznal, ale
zejména se již hotové (a odladěné!) moduly dají použít v dalších programech.
Priklad 10.1. |
Modulem může být například standardní knihovna pro práci se soubory,
která obsahuje funkce pro otevření, čtení, zápis do souboru atd. V Turbo Pascalu a jazyce C je tato knihovna realizována tak, že údaje
o fyzickém souboru (pozice pro čtení a zápis, odkaz na v vyrovnávací paměť
atd.) jsou uloženy v datové struktuře - proměnné typu soubor (FILE).
Tato proměnná se v programu používá jako argument při volání funkcí knihovny. Nevýhodou ovšem je, že s obsahem struktury zmíněné proměnné mohou manipulovat
kromě knihovny, které to přísluší, i jiné části programu a v případě přepsání
údajů může později dojít k jeho zhroucení apod. Bezpečnější by bylo, kdyby
k údajům této proměnné měly přístup výhradně funkce příslušné knihovny.
Řešením je uzavřít údaje spolu s příslušnými funkcemi do jednoho
modulu a místo datové struktury použít pouze odkaz (referenci). OOP jde v tomto směru ještě dále a s každým souborem (resp. s daty o souboru)
přímo sváže funkce, které s ním mohou výlučně manipulovat. Vše si lze
představit následovně: 
|
|
Objekt je runtime entita (1) skládající se
z proměnných, zvaných členské proměnné (member variables), a
příslušejících funkcí, zvaných metody (methods). V členských
proměnných je obsažen stav objektu, tj. vše, co si objekt
"pamatuje", a metody vyjadřují jeho chování, tedy vše, co objekt
"umí".
Priklad 10.2. |
Myšlenka objektů byla převzata z reálného světa. Jedním
z nejjednodušších reálných objektů je obyčejný vypínač - jeho stavem
je: poloha (vypnuto, zapnuto) a chováním: zapnutí a vypnutí. I zde je vše
uzavřeno uvnitř a nemáme možnost přímo (bez šroubováku) manipulovat
se stavem vypínače a přivodit si úraz.
|
|
Uzavřenost (encapsulation) je jednou z nejdůležitějších
vlastností objektů a má kromě bezpečnosti ještě výhodu skrytí vnitřní
implementace a modularity - softwarový objekt může být při zachování
rozhraní metod uvnitř zcela přeprogramován a může se libovolně měnit vnitřní
struktura dat (členských proměnných), ale není třeba přizpůsobovat okolí.
V programech se uzavřenost úplně striktně nedodržuje - například pokud objekt
reprezentuje pouze datovou strukturu (nemá metody), nebo z důvodu rychlosti -
a je možné povolit přímý přístup k libovolným členským proměnným (viz
11.4.). To by se však mělo týkat pouze těch proměnných,
jejichž změnou zvenčí nelze uvést objekt do nekonzistentního
("nesmyslného") stavu - např. u souboru nastavení pozice pro čtení na
zápornou hodnotu.
Jeden objekt se samozřejmě může obsahovat další, vše záleží na zvolené
rozlišovací schopnosti. Například pro některé aplikace lze počítač
reprezentovat jedním objektem, pro jiné se bude skládat z dalších
(motherboardu, harddisku, videokarty, řadiče atd.).
10.2. Zpráva
Samotné objekty vydělené ze světa by byly k ničemu, a proto spolu komunikují
pomocí tzv. zasílání zpráv.Zpráva je obecný pojem, jímž se označuje zaslání požadavku mezi
objekty bez ohledu na způsob přenosu, (2) jehož
výsledkem je volání metody cílového objektu. Zprávu obecně tvoří:
- jméno objektu, kterému je obsah určen,
- jméno metody, která se má vykonat,
- parametry metody (data).
Mechanismus zasílání zpráv tak umožňuje, že se komunikující softwarové objekty
mohou nalézat ve dvou různých procesech, programech nebo dokonce na dvou
různých počítačích. Pro jednotlivé situace však není třeba objekty
přizpůsobovat.
10.3. Třída
Většina podstatných jmen v našem jazyce označuje třídu živých,
neživých nebo abstraktních objektů. Například vaše škodovka a sousedův
Mercedes jsou objekty patřící do třídy aut. Třídou se v podstatě rozumí obecné
vlastnosti, které objekt musí mít, aby do ní mohl být zařazen - neboli: třída
je popisem, vzorem, předlohou celé skupiny podobných objektů.V programovacím jazyce je třída (class (3) ) přesně popsána. Z hlediska
syntaxe je třída obyčejnou deklarací datové struktury (podobně jako
record v Pascalu nebo struct v C) rozšířenou o metody. Třída
představuje objektový typ, objekt se nazývá instancí
třídy.
Priklad 10.3. |
Realizace vypínače z příkladu 10.2. v Javě může vypadat takto:
public class Vypinac {
private boolean poloha = false;
public void zapni() {
poloha = true;
}
public void vypni() {
poloha = false;
}
public void vypisStav() {
if (poloha)
System.out.println("Zapnuto.");
else System.out.println("Vypnuto.");
}
}
Třída Vypinac obsahuje veřejně přístupné (public) metody zapni() a vypni() a soukromou (private) členskou proměnnou poloha. Podle této třídy může program vytvořit teoreticky libovolné množství
nezávislých instancí:
public class Program {
public static void main(String[] args) {
Vypinac prvni = new Vypinac(); // vytvoření vypínače
Vypinac druhy = new Vypinac(); // vytvoření vypínače
prvni.zapni();
prvni.vypisStav();
druhy.vypisStav();
}
}
|
|
Dosud vše nasvědčuje tomu, že třída je pouhou deklarací objektů a v programu
fyzicky neexistuje - existují až její konkrétní objekty, které mají svůj stav
uložen v členských proměnných. I samotná třída však může mít proměnné (class variables) a metody (class methods), nazývané též statické. Statické proměnné existují jen v jednom exempláři
a všechny objekty dané třídy je sdílí - jsou tedy jakousi obdobou
globálních proměnných pro danou třídu. Statické metody (4) mají tu výhodu, že je lze volat, i když
(ještě) neexistuje žádná instance třídy - viz metoda main v příkladu
4.2..
10.4. Dědičnost
Jedna třída obvykle nestačí pro dostatečně přesnou klasifikaci podobných
objektů, například do třídy aut patří dodávky, nákladní, osobní auta atd.
Přitom podtřídy mají vlastnosti třídy nadřazené. Tuto myšlenku převzalo i OOP.Objektové jazyky umožňují konstruovat třídy tak, že od první (nejobecnější)
se mohou odvozovat další přidáním nebo překrytím (předefinováním)
metod (a příp. přidáním proměnných). Toto odvozování je možné dále opakovat a
výsledné třídy jsou pak uspořádány do stromové hierarchie:  Následník dané třídy ve stromu se nazývá potomek (subclass)
a předchůdce rodič (superclass), podobně jako v rodokmenu. Třída v daném uzlu s výjimkou kořene dědí (tj. přejímá, jako by byly
znovu definovány) všechny proměnné a metody rodiče (5) a tím,
nepřímo, i všech nadřazených tříd. Při procházení stromu shora dolů tak
získáváme čím dál specializovanější třídy. Díky dědičnosti se při programování lze vyhnout mnohým chybám (odvozená
třída používá již odladěný kód z rodiče) a ušetří se i opětovné psaní kódu
(metody rodičovské třídy není třeba znovu definovat).
Na rozdíl od některých jiných jazyků (např. C++) Java nepodporuje tzv.
vícenásobnou dědičnost, kdy může třída mít více než jednoho rodiče,
neboť to obvykle přináší větší obtíže než výhody (např. narůstá komplexnost
zdrojového textu). Případ, kdy by třída měla mít vlastnosti více rodičů, Java
obchází pomocí rozhraní (interface), viz 11.6..
V Javě jsou všechny třídy uspořádány do jediného stromu, jehož kořenem
je třída Object (viz 11.5.). Pokud v programu není uveden
rodič nové třídy, je jím automaticky právě Object. (6)
10.5. Vícetvarost
V předchozí kapitole bylo naznačeno, že pokud daná třída dědí metody rodiče,
jsou tyto přejaty a chovají se jako by byly v této třídě znovu definovány.To v podstatě vyjadřuje vícetvarost (polymorphism), která
říká, že jméno akce (metody) je sdíleno třídami ve stromu dědičnosti a ta je
implementována způsobem, který ji na dané úrovni přísluší. Stoprocentně to ovšem platí pouze o Javě, nikoliv například o Turbo
Pascalu či C++. V praxi jde o to, jak se ve zděděných metodách volají překryté
(předefinované) metody potomků:
Priklad 10.4. |
Máme třídu Bod, jejíž instance představují body na obrazovce.
Třída Bod obsahuje dvě metody:
- zobraz() - zobrazí bod na aktuálních souřadnicích,
- posun() - přesune bod o zadaný úsek (změní jeho souřadnice) a
následně zavolá metodu zobraz().
Třída Kruh je potomkem třídy Bod, dědí tedy obě uvedené metody.
Metoda zobraz() je však ve třídě Kruh překryta - zobrazí kruh. Při volání metod pak v Javě dostaneme výsledek, který
bychom asi očekávali:
- metoda posun() třídy Bod změní souřadnice a zobrazí bod,
- metoda posun() třídy Kruh změní souřadnice a zobrazí kruh.
V C++ či Turbo Pascalu dopadne výstup "kupodivu" takto:
- metoda posun() třídy Bod změní souřadnice a zobrazí bod,
- metoda posun() třídy Kruh změní souřadnice a zobrazí bod!
V programu je totiž kód metody posun() obou tříd sdílen a
instance obou tříd tedy používají fyzicky tutéž metodu. V C++ je
v metodě posun() "napevno" přeloženo volání metody zobraz() (7) - třídy Bod, a proto dojde k uvedenému
výsledku. Oproti tomu v Javě se v metodě posun() až za
běhu programu rozhoduje, která z metod zobraz() se bude
volat (8) . Jméno metody posun() je (v případě Javy) sdíleno oběma třídami a ta
funguje způsobem, který ji na dané úrovni přísluší (vícetvarost).
|
|
V C++ či Turbo Pascalu lze dosáhnout stejného výsledku jako v Javě tím, že
je metoda (zobraz()) deklarována jako virtuální. Pak se její
volání provádí nepřímo přes tzv. tabulku virtuálních metod. Z pohledu
C++ jsou tedy všechny nestatické metody v Javě virtuální.
|