Dědičnost a vkládání objektů
Třetí příklad ukazuje jak můžeme vytvořit novou třídu reprezentující dvojice čísel modulo M, N na základě hotové třídy z minulého příkladu.
// Objective C -- příklad 3/1, soubor sample3.h
//
// ukázka dědičnosti a skládání objektů
#import "sample2.h"
// nová třída bude reprezentovat dvojice čísel v algebře modulo MxN
| @interface ModMN: ModN | // třída se jmenuje ModMN a je dědicem třídy ModN |
{ // její objekty obsahují proměnné
| ModN *n2; | // druhá složka dvojice (vložený objekt) |
}
| +new:(int)M:(int)N; | // vytvoření nového objektu, nastavení modulů |
| -init:(int)M:(int)N; | // inicializace nového objektu, nastavení modulů |
| -set:(int)i:(int)j; | // nastavení na celočíselnou hodnotu <i,j> |
| -(int)get1; | // zjištění hodnoty první složky |
| -(int)get2; | // zjištění hodnoty druhé složky |
| @end | |
| // end of file |
Podívejme se podrobněji na jednotlivé části programu:
1. Rozhraní třídy -- sample3.h
Hlavičkový soubor 'sample2.h' musíme importovat, chceme-li vytvářet novou třídu jako dědice třídy ModN. Standardní hlavičkový soubor 'Object.h' je importován nepřímo prostřednictvím souboru 'sample2.h', nemusíme jej tedy zde importovat znovu (ale kdybychom to udělali nic by se nestalo).
Novou třídu implementujeme tak, že její objekty budou díky dědictví po třídě ModN samy reprezentovat první složku dvojice čísel; každý objekt si navíc automaticky vytvoří další pomocný objekt třídy ModN, který bude reprezentovat druhou složku dvojice. Přidáme tedy pouze jedinou proměnnou 'n2', která bude obsahovat identifikaci tohoto pomocného objektu.
// Objective C -- příklad 3/2
//
// ukázka implementace nové třídy s dědičností a skládáním objektů
#import "sample3.h"
@implementation ModMN
-(int)get1
{
return value;
}
-(int)get2
{
return [n2 get];
}
-set:(int)i
{
return [self set:i:i];
}
-set:(int)i:(int)j
{
[super set:i];
[n2 set:j];
return self;
}
-add:n
{
return [self set:value+[n get1]:[n2 get]+[n get2]];
}
-sub:n
{
return [self set:value-[n get1]:[n2 get]-[n get2]];
}
-init:(int)m:(int)n
{
[super init:m];
n2=[ModN new:n];
return self;
}
-init:(int)m
{
return [self init:m:m];
}
+new:(int)m:(int)n
{
return [[self alloc] init:m:n];
}
// kvůli uvolnění vloženého objektu musíme reimplementovat metodu free!!!
-free
{
// nejprve uvolníme vložený objekt
[n2 free];
return [super free];
}
@end
// end of file
Podívejme se podrobněji na jednotlivé části programu:
2. Implementace metod -- sample3.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 'get1' ukazuje, že metody mají přímý přístup i ke zděděným proměnným objektu -- použijeme-li tedy uvnitř metody proměnnou 'value', budeme pracovat s obsahem proměnné 'value', zděděné po třídě ModN. Poznamenejme, že Objective C nabízí mechanismus, kterým lze takový přístup k proměnným zakázat (požadujeme-li maximální flexibilitu); kdyby tomu tak bylo, museli bychom použít výraz '[super get]' podobně, jako je v metodě 'set::' použit výraz '[super set:i]'.
Metoda 'get2' je implementována očekávatelným způsobem. Metoda 'set:' je implementována prostřednictvím metody 'set::' tak, aby nastavila obě složky na stejnou hodnotu. Konečně sama metoda 'set::' prostě nastaví obě složky pomocí metody 'set:' implementované v původní třídě ModN -- v prvém případě to zajistí speciální příjemce 'super', ve druhém fakt, že objekt 'n2' je objektem třídy ModN.
Nyní se vrátíme k implementaci metody 'zero' ve třídě ModN (jen si ji nalistujte v minulém čísle). Kdybychom ji byli implementovali přímo příkazem 'value=0', museli bychom ji nyní reimplementovat. Použili jsme však flexibilnější '[self set:0]'; díky tomu můžeme zprávu 'zero' poslat i objektu třídy ModMN a bude pracovat korektně. Uvědomme si, proč tomu tak je:
pošleme-li objektu třídy ModMN zprávu 'zero', vyvolá se její implementace zděděná od třídy ModN (protože třída ModMN vlastní metodu 'zero' nemá);
v rámci této implementace objekt pošle sám sobě zprávu 'set:0';
dostane-li objekt zprávu 'set:', vyvolá se již její implementace ve třídě ModMN!
v jejím rámci objekt pošle sám sobě zprávu 'set:0:0';
implementace se opět samozřejmě nalezne na úrovni třídy ModMN a korektně nuluje obě složky dvojice.
Metody 'add' a 'sub' pak využívají metody 'set::'. Rozeberte si tyto metody podrobně a uvědomte si které objekty dostávají které zprávy a použije-li se implementace z třídy ModN nebo implementace z třídy ModMN!
Metoda 'init::' si nejprve vyžádá inicializaci zděděných proměnných příkazem '[super init:m]' a pak vytvoří pomocný objekt pro druhou složku dvojice a jeho identifikaci uloží do proměnné 'n2'. Metody 'init:' reimplementujeme prostřednictvím metody 'init::'. Uvědomme si, že -- podobně jako tomu bylo u zprávy 'zero' -- stačilo reimplementovat zprávu 'init:' a objekty třídy ModMN budou korektně zpracovávat i zprávu 'init' (protože v její implementaci objekt sám sobě pošle zprávu 'init:'). Pokud bychom vytvářeli novou třídu jako dědice třídy ModMN, stačilo by uvnitř ní reimplementovat metodu 'init::', aby objekty nové třídy korektně interpretovaly všechny zprávy 'init', 'init:' i 'init::'.
Na implementaci 'třídní' metody 'new::' není nic zvláště zajímavého. Uvědomme si, že díky reimplementaci metody 'init:' můžeme třídě ModMN posílat i zprávu 'new:', zděděnou od třídy ModN, aniž bychom ji museli reimplementovat.
Nakonec musíme reimplementovat ještě metodu 'free' -- v jejím rámci je nutno nejprve uvolnit vložený objekt 'n2' a pak si teprve příkazem '[super free]' vyžádat vlastní uvolnění objektu, který zprávu zpracovává.
// Objective C -- příklad 3/3
//
// ukázka použití nové třídy
#import <stdio.h>
| #import "sample3.h" | // importujeme interface |
void main()
{
ModMN *n1,*n2;
n1=[ModMN new:4:6];
n2=[ModMN new:20:12];
// inicializujeme obě čísla stejně, vzhledem k různým modulům však bude
// nastavená hodnota různá:
| #define INIT1 | 30 | |
| #define INIT2 | 40 |
[n1 set:INIT1:INIT2];
[n2 set:INIT1:INIT2];
printf("Init <%d,%d>, n1=<%d,%d>, n2=<%d,%d>\n",INIT1,INIT2,[n1 get1],[n1 get2],[n2 get1],[n2 get2]);
// vypíše "Init <30,40>, n1=<2,4>, n2=<10,4>"
// přičteme k prvnímu číslu druhé
[n1 add:n2];
printf("n1+n2=<%d,%d>\n",[n1 get1],[n1 get2]);
// vypíše "n1+n2=<0,2>"
[n1 free];
[n2 free];
}
// end of file
Použití nových metod by již mělo být jasné ze zdrovjového kódu.