Programování v prostředí Cocoa (3) S Kakaem a metodicky V minulém pokračování tohoto seriálu jsme se seznámili s pojmy objekt, zpráva a třída. Tentokrát náš krátký přehled základů objektového programování dokončíme. Jak jsme si minule řekli, protože třída zná všechny informace o objektech, jež reprezentuje, je přirozené, aby sama tyto objekty vytvářela. Je tu však další "nová věc" – neměli bychom podobně, jako jsme přidali do jazyka objekt (a operace nad ním, tj. zasílání zprávy), přidat do jazyka třídu a nějaké operace nad ní? Samozřejmě, bylo by to možné a například C++ to tak dělá. Existuje však daleko elegantnější řešení. Uvědomme si, že objekty jsme zavedli natolik obecně, že mohou dělat prakticky cokoli – proč by tedy třídy samy nemohly být objekty jako každé jiné? Pro komunikaci s třídami pak můžeme použít naprosto standardní mechanismus zpráv. Jen opět pro lepší čitelnost budeme pro třídy používat namísto typu id typ Class a místo hodnoty nil hodnotu Nil. Znovu ovšem připomeňme, že to děláme jen pro sebe, aby se nám lépe četly zdrojové texty. Překladači to je jedno a vše by fungovalo stejně dobře, i kdybychom používali kdekoli kterýkoli z trojice typů (včetně void*) a hodnot (včetně NULL). Přece jen ale jazyk o něco rozšířit musíme: o prostředky pro tvorbu tříd a pro popis toho, jak budou objekty zpracovávat zprávy. Poznamenejme, že třídy jsou standardními objekty až na jednu výjimku: samy již nemají žádnou "třídu tříd" čili metatřídu. Bylo by možné ji zavést a některé objektové systémy to skutečně dělají. Praktické výhody jsou však minimální. Rozhraní, properties, implementace a metody Popis třídy má dvě jasně oddělené části: rozhraní, které obsahuje informace o tom, jak se s jejími objekty pracuje (a kvůli dědičnosti i něco málo o jejich vnitřní struktuře), a implementaci, jež určuje, jak objekty budou zpracovávat zprávy. Ve zdrojových textech pro jejich popis slouží direktivy @interface, @implementation a @end. Nejjednodušší rozhraní prostě určí jméno nově vytvářené třídy. Pokud využíváme dědičnosti (což je v praxi téměř vždy), zapíšeme za jméno nové třídy dvojtečku a za ni jméno již existující třídy, od níž chceme novou děděním odvodit (budeme jí říkat nadtřída): @interface MyClass:NSObject @end Velice často by se nám hodilo, aby každý objekt třídy obsahoval nějaké vlastní proměnné (properties), jež tak či onak definují jeho obsah: objekt kniha by asi měl proměnné autor, název, vydavatel a podobně. Všechna objektová prostředí proto umožňují v rámci třídy takové proměnné definovat. Je celkem zřejmé, že se obsah těchto proměnných stane součástí toho "něčeho v paměti", co – jak víme z předešlého dílu – reprezentuje objekt. Ve zdrojovém textu můžeme takové proměnné definovat ve složených závorkách hned za jménem třídy a nadtřídy: @interface MyClass2:NSObject { // každý objekt třídy MyClass2 bude mít vlastní... int i,j; // ...dvě proměnné typu int... double d; // ...jednu typu double... id o1,o2,o3; // ...a tři (odkazy na) objekty. } @end (Připomeňme, že id je vlastně ukazatel – např. mezi proměnnou i a o2 je tedy určitý rozdíl, zřejmý zkušeným programátorům v C: číslo i leží skutečně uvnitř objektu třídy MyClass, zatímco objekt o2 je někde venku – uvnitř objektu třídy MyClass je jen odkaz na něj.) Pokud měla nějaké vlastní proměnné nadtřída, budou v definované třídě k dispozici také. Jinými slovy, vlastní proměnné kterékoli třídy zahrnují nejen ty, jež jsou deklarovány v jejím rozhraní, ale také všechny deklarované v její nadtřídě, v nadtřídě nadtřídy a tak dále až po "nejvyšší" třídu, která již nadtřídu nemá. Pečlivý čtenář odstavce, v němž jsem popisoval zprávy, se možná zarazil: zpráva intValue vracela číslo typu int, zpráva doubleValue vracela číslo typu double; tři argumenty zprávy drawCircleWithCentreX:Y:radius:title: byly typu int a čtvrtý char* – jak to má překladač vědět? Snadno: poslední standardní součástí rozhraní je totiž deklarace zpráv a jejich typů. Syntaxe je jednoduchá: před každou zprávu napíšeme znak '-', argumenty označíme identifikátory a před ně i před celou zprávu v závorkách napíšeme typy: @interface MyClass3:NSObject { ... } -(int)intValue; -(double)doubleValue; -(void)drawCircleWithCentreX:(int)x Y:(int)y radius:(int)r title:(char*)tt; @end Je důležité mít na paměti, že jde jen o informaci pro překladač! Za běhu pak díky pozdní vazbě může libovolný objekt dostat libovolnou zprávu bez ohledu na to, jestli je zapsaná v jeho rozhraní nebo ne. Můžeme mimochodem používat i zprávy, jež nejsou zapsané v žádném rozhraní: jejich návratové hodnoty i jejich případné argumenty pak budou typu id. Totéž platí pro návratové hodnoty nebo argumenty, u kterých žádný typ v závorce neuvedeme. Implementace z hlediska programátora vlastně není nic jiného než naprogramování několika metod. Metoda je v zásadě standardní "céčková" funkce – místo hlavičky funkce však použijeme hlavičku, která přesně odpovídá deklaraci zprávy v rozhraní (jen není zakončena středníkem). Překladač pak udělá dvě věci: (a) přeloží kód metody, (b) umístí do třídy informaci, že dostane-li kterýkoli její objekt zprávu odpovídající hlavičce metody, bude vyvolána právě tato metoda. Na rozdíl od deklarací v rozhraní tedy metody v implementaci skutečně popisují chování objektu: dostane-li objekt zprávu, jíž neodpovídá žádná z jeho metod, odmítne ji a dojde k běhové chybě (pro úplnost poznamenejme, že jsou k dispozici prostředky, jak programovat plné dynamické zpracování zpráv, tj. takové, že objekt může zpracovávat například libovolnou zprávu, jejíž jméno začíná na "a" a má sudý počet písmen; prozatím si však takovými věcmi nebudeme komplikovat život). @implementation MyClass3 -(int)intValue { return 1; } -(double)doubleValue { return 1.0; } -(char)charValue { return 'a'; } @end Povšimněme si, že metody v implementaci neodpovídají přesně zprávám z rozhraní. To, že v implementaci je metod více, je naprosto běžné: odpovídající zprávy z toho či onoho důvodu nejsou součástí rozhraní, ale objekty třídy MyClass3 je přesto dokážou zpracovat. Opačný případ (zpráva uvedená v rozhraní nemá metodu v implementaci) je méně obvyklý, ale také možný. Uvnitř implementace metod jsou přístupné všechny vlastní proměnné objektu (takže kdybychom například implementovali metodu třídy MyClass2, mohli bychom vracet hodnotu proměnné d příkazem return d;). Nakonec je třeba říci, že s odmítnutím zprávy a běhovou chybou jsem malinko lhal: pokud totiž není součástí implementace metoda pro přijatou zprávu, hledá se metoda v nadtřídě. Není-li ani tam, hledá se v její nadtřídě, a tak dále, dokud nenarazíme na "nejvyšší" třídu, která již nadtřídu nemá. Teprve nenajde-li se metoda ani tam, je zpráva odmítnuta. To pohodlně a automaticky zajišťuje dědění zpráv: jestliže v implementaci třídy NSObject byla metoda name, můžeme odpovídající zprávu posílat například objektům třídy MyClass3 bez obavy, že by byla odmítnuta. Metody tříd Připomeňme si, že třída sama je objektem a sama dokáže přijímat a zpracovávat zprávy. Proto můžeme v rozhraní kromě deklarace zpráv určených pro objekty deklarovat i zprávy určené pro samotnou třídu. Podobně v implementaci můžeme definovat metody, které budou vyvolány v případě, že třída sama dostane zprávu odpovídající hlavičce metody. V obou případech je deklarace i definice stejná jako minule, jen znak '-' na začátku je nahrazen znakem '+': @interface MyClass4:NSObject +alloc; // vrátí nový objekt této třídy +(char*)name; // pro třídu -(char*)name; // pro objekty @end @implementation MyClass4 +alloc { ... } +(char*)name { return "Třída MyClass4"; } -(char*)name { return "Objekt MyClass4"; } @end Poslední informace, která nám chybí k tomu, abychom mohli začít opravdu programovat, je, jak se dostaneme k "objektu třída" z programu. To je ale prosté: pokud jméno třídy použijeme v hranatých závorkách jako příjemce zprávy, reprezentuje právě požadovaný "objekt třída". Takže malé cvičení pro pozorné čtenáře: je jasné, co vypíše následující funkce, je-li použita po deklaraci a definici třídy MyClass4? void printout(void) { id o=[MyClass4 alloc]; printf("%s, %s",[MyClass4 name],[o name]); } Samozřejmě že metody tříd se dědí analogickým způsobem jako metody objektů: jestliže dostane třída zprávu, pro niž nenajde ve vlastní implementaci žádnou "plusovou" metodu, hledá metodu v implementaci své nadtřídy... Shrnutí Ukázali jsme si základní přístup k objektům a principy jejich používání. V rámci příkladů jsme se přitom seznámili s nejdůležitějšími součástmi jazyka Objective C. Ti, kdo mají jeho překladač k dispozici (jako GNU C je k dispozici na libovolné platformě, od Mac OS X přes všechny varianty Unixu až po DOS či Windows), skutečně již mohou začít programovat. Příště si ukážeme těch několik málo (skutečně málo a poměrně nevýznamných) prvků jazyka Objective C, na něž se zatím nedostalo. Pak se už začneme bavit o skutečných vlastnostech prostředí Cocoa: ukážeme si mechanismus tvorby a zániku objektů a podobně. Ondřej Čada 6/00: 620-Kakao3 (Au.Ondřej Čada - 5.05 n.str., 2 TS) Strana: 1 Chyba! Neznámý argument přepínače./4