Objective C
Principy objektového programování samozřejmě můžeme používat v jakémkoli programovacím jazyce; v některém to však jde snáze, v jiném hůř -- podobně, jako strukturovaně lze programovat v COBOLu i v PASCALu, v prvním případě to však dá dost práce. Dnes existuje řada jazyků, navržených přímo pro podporu objektově orientovaného programování; jedním z nich -- nadstavbou jazyka C, nazvanou Objective C -- se budeme zabývat v tomto článku. Nebudeme popisovat programovací jazyk C, ale zaměříme se pouze na rozšíření Objective C; pojmy jako 'objekt', 'třída', 'zpráva' nebo 'metoda' budeme používat bez podrobnějšího vysvětlení ve smyslu, ve kterém jsou definovány v úvodním článku 'Objektově orientovaná analýza a design'.
Měl bych snad začít alespoň orientačním srovnáním Objective C s daleko známějším C++. Objective C je doslova a do písmene objektová nadstavba jazyka C; nezavádí (téměř) žádná rozšíření kromě vlastního aparátu pro práci s objekty. Ten je postaven na klasickém modelu předávání zpráv ve stylu SmallTalku; syntaxe zpráv v Objective C je dokonce přímo převzata ze SmallTalku. Objective C je plně objektový systém využívající v plné míře pozdní vazbu, vyžaduje proto netriviální runtime podporu pro dynamickou komunikaci mezi objekty -- v ideálním případě podporovanou na úrovni operačního systému. Snad proto se s jeho implementacemi setkáváme daleko řidčeji. C++ naproti tomu není objektovým jazykem; jedná se o sadu vylepšení a doplňků k jazyku C, které jaksi mimochodem umožňují i (nepříliš pohodlnou) tvorbu programů metodami OOP. Takto vytvořené programy již nevyžadují objektový runtime systém, a mohou být proto bez problémů využívány v principiálně neobjektovém prostředí jakým je třeba MS DOS nebo MS Windows.
1. Neobjektová rozšíření
Objective C zachovává jednu z největších výhod původního jazyka C -- jeho jednoduchost. Nezavádí vůbec žádné nové příkazy; pouze několik direktiv a typů potřebných pro OOP a jednu novou jazykovou konstrukci -- odeslání zprávy zadanému objektu. To samozřejmě přináší řadu výhod:
zkušený programátor v C se Objective C může naučit doslova přes víkend;
překladač Objective C je jen o málo složitější než překladač jazyka C;
programy psané v C lze až na nepatrné výjimky bez problémů překládat překladačem Objective C
Jazyková rozšíření Objective C nesouvisející s objekty jsou pouze tři:
1.1. komentář '//'
Objective C doplňuje do jazyka C komentář uvozený dvěma lomítky a ukončený koncem řádky (stejné komentáře podporuje i C++).
1.2. typ BOOL
To, že jazyk C nemá speciální typ pro logické hodnoty, je jednou z jeho zásadních výhod; může to však přinášet problémy při spojování programů, které používají fakticky různé typy pro tyto hodnoty a různé hodnoty pro 'pravdu'. Objective C proto zavádí typ BOOL a odpovídající hodnoty YES a NO. V 'obyčejném C' bychom mohli téhož dosáhnout například definicí
typedef enum {NO, YES} BOOL;
1.3. direktiva #import
Standardní direktiva #include pro vložení jiného souboru má jednu nepříjemnou nevýhodu -- umožňuje vložit jeden a tentýž soubor několikrát. Uvedeme-li tedy na začátku programu direktivy
#include <stdarg.h>
#include <stdio.h>
a obsahuje-li soubor stdio.h direktivu '#include <stdarg.h>', bude se soubor stdarg.h překládat dvakrát. V 'obyčejném C' se problém řeší poměrně nepohodlnými konstrukcemi typu
#ifndef _STDIO_H_
#define _STDIO_H_
...
#endif
v Objective C si můžeme ušetřit práci využitím direktivy #import namísto starého #include; překladač pak zajistí, že každý zdrojový soubor se bude překládat nejvýše jednou.
2. Nové typy a hodnoty
Kromě typu BOOL, který již známe, zavádí Objective C několik dalších typů, umožňujících práci s objekty:
| id | objekt |
| Class | třída |
| SEL | zpráva |
| IMP | metoda (přímý ukazatel na metodu, používaný pro statický přístup) |
Základním novým typem je samozřejmě id, reprezentující objekt. Ve skutečnosti se nejedná o nic jiného, než o ukazatel na data, která objekt v paměti reprezentují, a je definován jako void*. S ostatními typy se setkáme poměrně zřídkakdy: typ Class je doplněn pouze pro lepší přehlednost, protože třída je v Objective C objekt jako každý jiný a Class je tedy zcela ekvivalentní typu id. Typ SEL reprezentuje zprávu a je definován jako celočíselná hodnota, na kterou je zpráva interně přeložena. Konečně typ IMP vlastně není ničím jiným, než ukazatelem na funkci.
V souvislosti s novými typy nabízí Objective C i dvě nové hodnoty: nil je hodnota typu id a reprezentuje neexistující objekt, podobně Nil je hodnota typu Class a reprezentuje neexistující třídu. Obě nové hodnoty jsou zavedeny jen pro zvýšení čitelnosti programu a jsou zcela ekvivalentní 'obyčejnému' NULL.
3. Standardní identifikátory
Objective C nepřidává k jazyku C žádné nové klíčové slovo a definuje pouze tři standardní identifikátory self, super a _cmd. První z nich v implementaci metody reprezentuje objekt, který metodu zpracovává, druhý také, ale jeho metody jsou vyhledávány v rodičovské třídě. Třetí identifikátor reprezentuje zpracovávanou zprávu; využívá se pro rozlišení v případech kdy je několik zpráv zpracováváno v rámci jedné metody.
Díky tomu že self, super a _cmd jsou identifikátory a ne klíčová slova, můžeme je bez jakýchkoli problémů předefinovat; překladač Objective C proto bez problémů přeloží 'obyčejný céčkový' program, ve kterém je použita např. proměnná jménem self.
4. Direktivy
Prakticky všechny 'novinky', které Objective C přináší, jsou reprezentovány direktivami syntakticky podobnými direktivám preprocesoru, uvedenými ale znakem '@' namísto znaku '#'. Direktivy můžeme rozdělit do tří skupin:
4.1. Deklarace nových tříd
@interface
...
@end
Mezi direktivami @interface a @end je popsáno tzv. interface třídy. To je tvořeno deklaracemi proměnných, které reprezentují objekt deklarované třídy, a deklaracemi metod, které třída nabízí. Deklarace proměnných v podstatě nejsou ničím jiným, než starou známou deklarací struktury (pouze bez klíčového slova struct); deklarace metod jsou pak velmi podobné hlavičkám funkcí, jen mají mírně pozměněnou syntaxi kvůli přiblížení zprávám.
@implementation
...
@end
Direktivy @implementation ... @end obsahují tzv. implementaci třídy, tj. zdrojový kód jejích metod.
@protocol
...
@end
Direktiva @protocol uvádí deklaraci tzv. protokolu. Protokol v zásadě není ničím jiným, než seznamem metod; používá se jako společný prvek pro specifikaci tříd, které mají mít společné metody, ale nejsou strukturálně příbuzné (čímž nahrazuje implementačně i programátorsky obtížnou vícenásobnou dědičnost C++ v tom jediném případě, kdy měla jakýsi smysl).
4.2. Přístup k proměnným
Proměnné objektu mohou být k dispozici pouze jeho vlastním metodám, nebo i metodám všech jeho dědiců, nebo -- ve vyjímečných případech, kdy z nějakého důvodu musíme rezignovat na OOP a využívat statické programátorské techniky -- mohou být proměnné přístupné z jakéhokoli úseku kódu. Možnosti přístupu k proměnným jsou určeny použitím jedné ze tří direktiv:
| @private | proměnné jsou přístupné pouze metodám objektu samotného; |
| @protected | proměnné jsou přístupné i dědicům (tento přístup je standardní nepoužijeme-li žádnou z direktiv); |
| @public | proměnné jsou přístupné komukoli. |
4.3. Ostatní direktivy
Objective C nabízí několik dalších, nepříliš často využívaných direktiv:
| @class | deklarace jména třídy, definované jinde; |
| @selector() | převod symbolického jména zprávy na typ SEL; |
| @protocol() | vyhledání protokolu zadaného jménem; |
| @encode() | převod symbolického jména typu na jeho znakovou reprezentaci; |
| @defs() | deklarace proměnných zadané třídy (pro statický přístup). |
Za zvláštní zmínku stojí direktiva @encode, která umožňuje dynamickou práci s typy. Standardní jazyk C sice umožňuje předávat např. jako parametr funkce hodnotu typu, určeného až při běhu programu (např. využitím ukazatele void*), nenabízí však žádnou standardní možnost, jak funkci předat informaci o tom, o jaký typ se jedná. V Objective C můžeme pohodlně využít direktivu @encode, která libovolný typ kóduje do řetězce znaků; ten pak můžeme uvnitř funkce snadno dekódovat. V tomto článku není dostatek místa na podrobný popis kódování; uvedeme proto pouze jediný jednoduchý příklad: typ 'int *[12]' bude reprezentován řetězcem "[12^i]".
5. Zprávy
Objective C zavádí novou jazykovou kostrukci pro předání zprávy objektu; tato kostrukce je výrazem (a může tedy samozřejmě stát kdekoli, kde smíme uvést nějaký výraz -- třeba v inicializační části příkazu for). Syntaxe je velmi jednoduchá:
[příjemce zpráva]
kde 'příjemce' je libovolný výraz typu id (nebo typu Class, který je s ním ekvivalentní) a určuje objekt, který má zprávu dostat. 'Zpráva' pak má syntaxi převzanou ze SmallTalku -- její identifikátor může obsahovat jednu či více dvojteček, každá dvojtečka reprezentuje jeden parametr. Uveďme několik příkladů zpráv: simpleMessage je zpráva bez parametru, setValueTo:10 je zpráva s jedním -- v tomto případě celočíselným -- parametrem a konečně třeba zpráva insertString:"Vložit" toString:"Semka><to přijde" toPosition:6 je příklad zprávy se třemi parametry. Povšimněme si, že díky vkládání parametrů přímo do identifikátoru zprávy je celý výraz daleko čitelnější a srozumitelnější, než kdybychom použili klasickou 'céčkovou' syntaxi s předáváním parametrů v závorkách za jménem zprávy.
6. Využití, příklady
Mikroskopický rozsah tohoto článku neumožňuje podrobně popsat způsoby využití konstrukcí, které Objective C pro objektové programování nabízí. Nemůžeme bohužel ani shrnout služby standardní třídy Object, která je obvykle využívána jako univerzální rodičovská třída a která díky dědičnosti dává libovolnému objektu širokou paletu velmi univerzálních schopností. Čtenář se proto zatím musí spokojit s několika bohatě komentovanými příklady jednoduchých programů, které doprovázejí tento článek; s výjimkou příkladů 1 a 7, které ukazují využití hotové knihovní třídy, nevyžadují příklady nic jiného než překladač Objective C se standardními knihovnami.
Příklad 1: využití předdefinovaných tříd (knihovny tříd NeXTstepu)
Příklad 2: tvorba vlastní třídy
Příklad 3: dědičnost a vkládání objektů
Příklad 4: dynamické rozpoznání třídy
Příklad 5: skládání objektů a dynamické rozpoznání zpráv
Příklad 6: skládání objektů a dynamické rozpoznání zpráv
Příklad 7: mechanismus klient/server
Příklad 8: statický přístup k objektům