Základy OOP

Zatímco při standardním programování se program skládal z dat a procedur, které nad nimi pracovaly:

je objektový program skupinou objektů, které spolu komunikují. Každý objekt přitom je kombinací dat i procedur -- např. textový řetězec je reprezentován objektem, který obsahuje jak data (tj. vlastní obsah řetězce), tak i procedury, které nad textem pracují: připojení dalšího řetězce, vyhledání podřetězce a podobně. Dobrou představu o objektu dá naprosto klasický obrázek:

Často se setkáváme s mnoha objekty stejného typu: v systému pro nemocnici bude zřejmě řada objektů 'oddělení', ještě více objektů 'lékař'... O takových objektech říkáváme že jsou objekty téže třídy ('class'). Všechny objekty téže třídy mají stejnou strukturu vnitřních dat a stejnou sadu služeb, které s těmito daty dokáží pracovat. Nedávalo by proto dobrý smysl, aby informace o struktuře a služby byly součástí každého objektu; namísto toho jsou součástí třídy samotné a objekty obsahují pouze odkaz na svou třídu. Třídy samy jsou tedy v podstatě také objekty; pro každou skupinu objektů stejného typu existuje jeden objekt, který odpovídá jejich třídě:

Ujasněme si vztah 'obyčejných objektů' a tříd:

třídy jsou statické v tom smyslu, že při spuštění programu již všechny existují a zaniknou až jeho ukončením (ve skutečnosti je možné doplňovat třídy za běhu, ale nic nepokazíme když na tuto možnost prozatím zapomeneme);
třída sama může nabízet nejrůznější služby, ale jejím základním úkolem je na požádání vytvořit 'obyčejný objekt', který třídě odpovídá (takovým objektům budeme často říkat 'instance');
objektům reálného světa až na výjimky odpovídají instance; třídy slouží pouze pro jejich vytváření;
třídy obsahují informace společné pro všechny jejich instance (např. kód jejich služeb);
jinak ale mezi třídou a instancí z hlediska programu další rozdíly nejsou: obě jsou objekty, a s oběma můžeme komunikovat stejným způsobem; hned si vysvětlíme jakým.

Objekty vzájemně komunikují prostřednictvím tzv. zpráv ('messages'). Na zprávu se můžeme dívat jako na 'balíček' který jeden objekt pošle druhému; v tomto balíčku je uloženo jméno zprávy a její případné parametry. Dostane-li objekt zprávu, dekóduje ji a sám se na jejím základě rozhodne kterou ze svých služeb provede (také může zprávu ke zpracování předat jinému objektu, nebo ohlásit chybu "neumím zpracovat tuto zprávu").

Uvědomme si, že mechanismus zpráv nese řadu výhod; jmenujme tři nejdůležitější:

polymorfismus: různé objekty mohou reagovat na jednu a tutéž zprávu 'po svém':

Můžeme například poslat libovolnému objektu zprávu copy: a objekt vytvoří svou kopii. Je samozřejmě naprosto odlišný úkol vytvořit kopii textového řetězce a vytvořit kopii dejme tomu okna na obrazovce; o to se však odesilatel zprávy nemusí starat. Přímým důsledkem je to, že jediný úsek programu může beze změny pracovat s nejrůznějšími objekty -- stačí, jestliže jsou schopny zpracovat stejné zprávy.

snad ještě důležitější je nezávislost na kanálu, kterým je zpráva předávána. Objekty si mohou posílat 'balíčky', aniž by přesně znaly své umístění -- operační systém se postará o korektní předání zprávy třeba prostřednictvím lokální sítě na jiný počítač. Tento mechanismus je, mimochodem, základem technologie Distributed Objects.

možnost implementovat rozumným způsobem dědičnost -- té se budeme věnovat hned v příštím odstavci.

Poznamenejme, že pro samotné služby, které jsou součástí objektů, někdy používáme termín metody ('methods'). Platí: dostane-li objekt zprávu, vyhledá odpovídající metodu (je-li taková) a tu provede. Obvykle objekt obsahuje pro každou ze zpráv, které zpracovává, právě jednu metodu; proto se tyto pojmy často zaměňují. Je však třeba mít na paměti rozdíly: zpráva je 'balíček', který si objekty posílají; metoda je kód, který objekt zavolá na základě přijetí zprávy. Jedna zpráva tedy může vyvolat v různých objektech provedení různých metod.



Dědičnost

Základní princip dědičnosti je -- podobně jako objekty samy -- odvozen z reality: známe-li židli a chceme-li popisovat křeslo, řekneme pravděpodobně "to je vlastně židle s těmito několika drobnými rozdíly:...". Podobně je tomu v objektovém prostředí -- vytváříme-li novou třídu, můžeme využít kteroukoli z již existujících tříd a popsat pouze rozdíly mezi nimi.

Připomeňme, že služby objektů jsou ve skutečnosti obsaženy ve třídách -- dostane-li tedy instance nějakou zprávu, vyhledá odpovídající metodu ve své třídě a tu provede. Právě tento mechanismus umožňuje korektní implementaci dědičnosti: každá třída si pamatuje svého 'rodiče', tj. třídu, od které byla odvozena. Jestliže nyní objekt požadovanou metodu ve 'své' třídě nenalezne, pokusí se ji prostě vyhledat v její rodičovské třídě:

dostane-li objekt třídy, zobrazené vlevo dole, zprávu odpovídající vyvolání metody W, nalezne metodu ve své třídě a prostě ji provede. Dostane-li však zprávu odpovídající vyvolání metody A, nenalezne ve své třídě nic -- přejde proto k její rodičovské třídě, kterou vidíme v horní části obrázku; v ní metodu nalezne a provede ji.

Z hlediska odesilatele zprávy to vlastně znamená, že objekt je schopen zpracovat nejen všechny zprávy, pro které jsou v jeho třídě připraveny metody, ale i všechny zprávy, pro které jsou metody ve všech předchůdcích jeho třídy v hierarchii dědičnosti.



'Objektové' a 'třídní' metody

Ačkoli to již nepřímo bylo řečeno, je vhodné si uvědomit, že každá třída -- díky tomu, že udržuje informace společné všem svým objektům -- obsahuje dvě skupiny metod:

vlastní metody třídy, ze kterých se vybírá v případě kdy zprávu dostane sama třída;
metody pro instance, ze kterých se vybírá v případě, kdy zprávu dostane instance.

Prohledávání obou skupin využívá posloupnosti dědiců, popsané v minulém odstavci -- jestliže tedy dostane třída zprávu, pro kterou sama nemá (třídní) metodu, hledá se dále mezi (třídními) metodami její rodičovské třídy.



Programovací jazyk Objective C

Programovací jazyk Objective C ideálně odpovídá popsanému objektovému systému. Navíc dodržuje původní výhodu klasického jazyka C -- je velmi, velmi jednoduchý.

Objective C je nadstavbou, která stojí nad klasickým (ANSI) C -- to znamená, že až na naprosté výjimky lze každý program v ANSI C bez problémů přeložit i překladačem Objective C. Nebudeme zde popisovat všechny prvky této nadstavby, ale soustředíme se pouze na ty nejdůležitější.



Zprávy a metody

Objective C používá pro zprávy a pro metody stejnou syntaxi; díky tomu je snadno srozumitelný standardní mechanismus výběru: dostane-li objekt nějakou zprávu, vyhledá a provede metodu se stejným jménem (pokud taková existuje v jeho třídě nebo v některé z tříd rodičovských). Chceme-li, nabízí samozřejmě Objective C i prostředky k tomu, abychom v rámci kterékoli třídy implementovali vlastní algoritmus výběru metod.

Syntaxe zpráv a metod vychází ze SmallTalku: jménem zprávy nebo metody je libovolný identifikátor, který může kromě běžných znaků navíc obsahovat dvojtečky; každá dvojtečka označuje jeden parametr. Tak můžeme mít zprávy (nebo metody)

zpravaBezParametru
zpravaSJednimParametrem:x
zpravaSeDvemaParametry:x:y

Čitelnost programů v Objective C nesmírně zvyšuje to, že dvojtečka (a jí odpovídající parametr) může stát kdekoli uvnitř identifikátoru zprávy:

oknoSirky:10 aVysky:20 naPoziciX:100 Y:100
podretezecOdPozice:5 vDelce:10

Pro odesílání zpráv zavádí Objective C nový výraz [<příjemce> <zpráva>], kde příjemce je obecný výraz jehož výsledkem je identifikace objektu (tj. výraz typu id -- viz níže), a zpráva je zpráva ve formátu popsaném výše. Hodnotou celého výrazu [...] je hodnota vrácená vyvolanou metodou.

Při deklaraci metod musíme mít navíc možnost určit
patří-li metoda třídě nebo instanci
vrací-li metoda hodnotu a jakého typu
typy případných parametrů.

Objective C zde zavádí jednoduchý standard: jméno metody je v deklaraci uvedeno vždy znakem '+' -- pak se jedná o metodu třídy -- nebo znakem '-', a pak se jedná o metodu instance. Typ návratové hodnoty nebo parametru je určen běžným deklarátorem jazyka C, uzavřeným do závorek; jestliže jej vůbec neuvedeme, předpokládá Objective C typ 'objekt' (typ id -- viz níže).

+alloc;

je tedy deklarace metody třídy, která vrací objekt (standardně všechny třídy po obdržení zprávy alloc vytvoří novou instanci);

-(void)setString:(char*)str withLength:(int)n;

deklaruje metodu setString:withLength:, která nevrací nic a má dva parametry -- první typu char* a druhý celočíselný.



Třídy a objekty

Všechny třídy jsou v Objective C pojmenovány a jejich jména lze v jazyce použít jako identifikátory s globální platností. Podle místa použití může mít tento identifikátor dvojí význam:

Na místě příjemce zprávy ve výrazu [...] reprezentuje třídu jako objekt, takže např.

[NSString alloc];

je odeslání zprávy alloc třídě NSString.

Ve všech ostatních konstrukcích může jméno třídy sloužit jako jméno typu, reprezentujícího instanci této třídy:

sizeof(NSString)

je velikost instance třídy NSString.

Objective C zavádí nový typ id, který reprezentuje libovolný objekt:

id s=[NSString alloc];
[s initWithCString:"Nazdar"];

Chceme-li aby překladač kontroloval odesíláme-li objektu pouze zprávy, které je schopen zpracovat, můžeme deklarovat objekt pomocí jména třídy (které, jak víme, reprezentuje typ 'instance'):

NSString *s=[NSString alloc];
[s tutoZpravuNSStringNezna];  // zde překladač vydá varování



Protokoly a ostatní

Třídy a jejich dědičnost nabízejí velmi silný mechanismus; neumožňují však explicitně specifikovat požadavek "objekt se kterým chci spolupracovat musí být schopen zpracovat následující zprávy: ...". Přitom je potřeba takovéto specifikace velmi častá -- píšeme-li univerzální úsek kódu (a v objektovém prostředí je téměř každý úsek dobře napsaného kódu univerzální), nezajímá nás třída objektu, se kterým budeme spolupracovat, ale právě jen skupina zpráv, které mu budeme posílat.

Objective C proto nabízí mechanismus pro formalizaci tohoto požadavku, nazvaný protokol. Protokol není ničím jiným než pojmenovaným seznamem zpráv; programátor pak má k dispozici prostředky pro zjištění, zda určitý objekt odpovídá určitému protokolu (tj. je schopen zpracovat kteroukoli z jeho zpráv) nebo ne.



Neobjektová rozšíření

Překladač Objective C přináší několik málo rozšíření, které přímo nesouvisí s objekty. Nejdůležitější z nich -- a jediné o kterém se zde zmíníme -- je zavedení standardního typu BOOL a hodnot YES a NO; přesně stejného efektu bychom v ANSI C dosáhli deklarací

typedef enum {NO,YES} BOOL;






(další článek)


Copyright (c) Ondra Čada