Tvorba vlastní třídy
Tentokrát se seznámíme s prostředky, které Objective C nabízí pro tvorbu vlastních tříd. Vytvoříme třídu, která umožní práci s čísly modulo N (přičteme-li k hodnotě N-1 jedničku, dostaneme v této algebře nulu) a na triviálním příkladu ukážeme jak nová třída pracuje.
Program uložíme do tří souborů: soubor 'sample2.h' bude obsahovat tzv. rozhraní třídy (interface); kdokoli kdo bude chtít naši třídu používat pak bude moci rozhraní importovat. Druhý soubor, 'sample2.m', obsahuje tzv. implementaci třídy -- v něm je popsáno chování objektů při přijetí zprávy. Poslední soubor -- 'sample2m.m' -- obsahuje program, který služeb nové třídy využívá (podobně jako minulý příklad využíval služeb třídy NXStringTable).
// Objective C -- příklad 2/1, soubor sample2.h
//
// ukázka interface nové třídy
#import <objc/Object.h>
| @interface ModN: Object | // třída se jmenuje ModN a je dědicem třídy Object |
{ // její objekty obsahují proměnné:
| int N; | // N -- modul | |
| int value; | // hodnota čísla |
}
// následující metody jsou metodami _třídy_ ModN, nikoli jejích objektů
// překladači to sdělíme úvodním znaménkem '+':
| +(int)minN; | // minimální modul (vrací číslo) |
| +new:(int)N; | // tvorba nového objektu se zadaným modulem |
// následující metody jsou interpretovány _objekty_ třídy ModN
// překladači to sdělíme úvodním znaménkem '-':
| -init:(int)N; | // inicializace nového objektu, nastavení modulu |
| -set:(int)i; | // nastavení na celočíselnou hodnotu i |
| -(int)get; | // zjištění hodnoty |
| -add:n; | // přičtení čísla n (není-li uveden typ, je parametr typu id) |
| -sub:n; | // odečtení čísla n |
| -zero; | // vynulování čísla n |
| @end | |
| // end of file |
Podívejme se podrobněji na jednotlivé části programu:
1. Import souboru Object.h -- sample2.h
Hlavičkový soubor 'Object.h' obsahuje interface standardní třídy Object, která je součástí každé implementace Objective C. Importujeme jej proto, že budeme vytvářet novou třídu jako dědice třídy Object.
2. Direktiva @interface -- sample2.h
Direktiva '@interface' v Objective C uvádí rozhraní třídy. Za direktivou se uvádí jméno nově vytvářené třídy, dvojtečka a jméno třídy, kteoru chceme použít jako vzor. Zde tedy vytváříme třídu jménem ModN jako dědice třídy Object.
Rozhraní třídy je ukončeno direktivou '@end', kterou vidíme na samém konci souboru 'sample2.h'.
3. Deklarace proměnných -- sample2.h
Bezprostředně za direktivou '@interface' následuje deklarace proměnných objektu ('properties') -- jedná se o blok proměnných, který bude každý objekt třídy ModN reprezentovat v paměti (společně s případnými dalšími proměnnými, zděděnými po třídě Object).
Deklarace proměnných je přesnou analogií deklarace struktury: mezi dvojicí složených závorek deklarujeme libovolné proměnné. Objekty třídy ModN tedy budou obsahovat dvojici celočíselných proměnných: hodnotu 'N', určující modul, a momentální hodnotu objektu uloženou v proměnné 'value'.
4. Deklarace metod -- sample2.h
Za deklarací proměnných objektu následuje deklarace jeho metod. Jméno metody je stejné jako jméno zprávy, která tuto metodu vyvolá; můžeme se tedy na deklaraci metod dívat také jako na seznam zpráv, které bude objekt nové třídy schopen zpracovat.
Deklarace metod je podobná deklaraci funkcí; parametry však uvádíme za dvojtečkami a typy zapisujeme do závorek. Neuvedeme-li žádný typ, předpokládá Objective C typ 'id'.
V Objective C je třída sama také objektem; může proto sama zpracovávat zprávy. Deklaraci metod třídy uvedeme znakem '+'; deklaraci metod objektu znakem '-'. Metody třídy mají dva hlavní účely:
většina metod třídy slouží pro vytváření nových objektů. Základem jsou metody 'alloc' a 'new', zděděné po třídě Object; jako příklad zde implementujeme metodu 'new:', která vytvoří objekt inicializovaný na zadanou hodnotu modulu.
jiné metody třídy podávají informace o objektech jako celku (ne o jednom konkrétním objektu); poměrně typickým využitím je specifikace definičních oborů. Jako příklad implementujeme metodu 'minN', která vrátí minimální přípustnou hodnotu modulu.
// Objective C -- příklad 2/2
//
// ukázka implementace nové třídy
| #import "sample2.h" | // importujeme interface |
| @implementation ModN | // rodiče a proměnné není třeba uvádět -- jsou známy z |
| interface |
| +(int)minN | // metody se definují stejně jako běžné funkce |
| { | |
| } | |
| -(int)get | |
| { |
return value;
}
-set:(int)i
{
value=i%N;
return self;
}
-zero
{
return [self set:0];
}
-add:n
{
return [self set:value+[n get]];
}
-sub:n
{
return [self set:value-[n get]];
}
-init
{
return [self init:10];
}
-init:(int)n
{
[super init];
if (n<[[self class] minN]) N=[[self class] minN];
else N=n;
| return [self zero]; | // vynulujeme |
}
+new:(int)n;
{
return [[self alloc] init:n];
}
@end
// end of file
Podívejme se podrobněji na jednotlivé části programu:
5. Direktiva @implementation -- sample2.m
Direktiva '@implementation' v Objective C uvádí implementaci třídy. Za direktivou se uvádí jméno nově vytvářené třídy; ostatní údaje překladač převezmě automaticky z rozhraní. Implementace je ukončena stejně jako rozhraní direktivou '@end'. Povšimněme si také přípony '.m'. Objective C používá standardně tuto příponu pro moduly, obsahující implementace tříd.
6. Implementace metod -- sample2.m
Implementace metod je přesnou obdobou implementace funkcí v běžném jayzce C: zopakujeme hlavičku metody, a namísto středníku za ni zapíšeme blok obsahující tělo metody.
Implementace metody 'minN' je zcela triviální. Metoda 'get' ukazuje pouze to, že metody mají přímý přístup k proměnným objektu -- použijeme-li tedy uvnitř metody proměnnou 'value', budeme pracovat s obsahem proměnné 'value' uvnitř objektu, který zprávu dostal.
Metoda 'set:' navíc ilustruje konvenci, která platí v Objective C: nemá-li některá metoda co rozumného vrátít, vrátí identifikaci objektu, který zprávu zpracovává; tuto identifikaci máme kdykoli k dispozici ve speciální proměnné 'self'. Díky této konvenci můžeme zprávy pohodlně řetězit -- můžeme například používat výrazy typu '[[[obj init:3] set:2] add:n]'.
V implementaci metody 'zero' vidíme, že proměnnou 'self' lze použít také chce-li objekt poslat zprávu sám sobě: objekt vynuluje svůj obsah prostřednictvím metody 'set:'. Tento přístup je flexibilnější než kdybychom použili přímo výraz 'value=0'; připomeneme si jej v příštím pokračování tohoto seriálu. Metody 'add' a 'sub' jsou implementovány podobně; obě navíc pouze zjistí pomocí zprávy 'get' jaká je hodnota objektu, který má být přičten nebo odečten. Konečně metoda 'init' je analogicky implementována nepřímo pomocí metody 'init:'; i k tomu se vrátíme v příštím dílu.
Metoda 'init:' nejprve musí zajistit inicializaci zděděných vlastností objektu. Použije tedy speciálního příjemce 'super'; ten zajistí -- podobně jako proměnná 'self' -- že objekt odešle zprávu sám sobě; navíc však se při použití příjemce 'super' použije implementace metody init uvnitř nadřízené třídy. To je důležité -- pokud by se použila standardním způsobem implementace uvnitř této třídy, byla by vyvolána metoda 'init' která sama opět volá metodu 'init:' -- došlo by tedy ke vzájemnému rekurzivnímu volání, které by bylo ukončeno až přetečením zásobníku.
Nakonec si uvědomíme, jak je implementována 'třídní' metoda 'new:'. Jedná se o metodu třídy, proměnná 'self' tedy reprezentuje třídu. Standardní zpráva 'alloc', odeslaná třídě, vytvoří nový objekt a vrátí jeho identifikaci; tomuto novému objektu tedy je ihned odeslána zpráva 'init:'.
// Objective C -- příklad 2/3
//
// ukázka použití nové třídy
#import <stdio.h>
| #import "sample2.h" | // importujeme interface |
void main()
{
ModN *n1;
id n2;
n1=[ModN new:4];
n2=[ModN new:20];
| if (0) { | // don't do it! |
| [n1 alloc]; | // překladač ohlásí varování | |
| [n2 alloc]; | // překladač nevaruje |
}
// inicializujeme obě čísla stejně, vzhledem k různým modulům však bude
// nastavená hodnota různá:
| #define INIT | 30 |
printf("Init %d, n1=%d, n2=%d\n",INIT,[[n1 set:INIT] get],[[n2 set:INIT] get]);
// vypíše se "Init 30, n1=2, n2=10"
printf("n1+n2=%d\n",[[n1 add:n2] get]);
// vypíše se "n1+n2=0"
[n1 free];
[n2 free];
}
// end of file
Podívejme se podrobněji na jednotlivé části programu:
7. Použití nové třídy -- sample2m.m
Samo použití nové třídy je triviální -- prostě vytvoříme dva objekty třídy ModN, určíme jejich hondoty, sečteme je a to je vše. V příkladu 'sample2m.m' se však navíc seznámíme s dalším jazykovým prvkem Objective C.
Známe-li předem třídu, můžeme namísto obecné deklarace 'id' pro identifikaci objektu použít také typ '<třída>*' -- proměnnou 'n1' tedy deklarujeme jako identifikaci objektu třídy ModN, zatímco proměnná 'n2' je deklarována jako identifikátor objektu libovolné třídy. K čemu je taková deklarace dobrá ilustruje příklad 'if (0)...': objekty třídy ModN nejsou schopny reagovat na zprávu 'alloc' a kdybychom jim ji poslali, program by skončil běhovou chybou. V prvním případě však překladač -- na základě deklarace -- ví, že 'n1' reprezentuje objekt třídy ModN a při překladu vydá varování.