Skládání objektů a dynamické rozpoznání zpráv
V pátém příkladu se seznámíme s komplikovanějším mechanismem, který v Objective C umožňuje dynamické rozpoznávání zpráv: při implementaci třídy můžeme sami určit kód, který bude přidělovat každé zprávě metodu, která ji má zpracovat. Zároveň si ukážeme další mechanismus, který Objective C nabízí: jedná se o přesměrování zpráv -- objekt si může zpracování zprávy vyžádat od jiného objektu.
Příklad bude odpovídat vícenásobné dědičnosti, známé z jiných jazyků. Vytvoříme novou třídu, která bude 'dědit' po skupině tříd Class_1 až Class_N; toto dědění zajistíme tak, že objekt naší třídy si vytvoří objekty všech 'vnořených' tříd a dostane-li nějakou zprávu, předá ji ke zpracování některému z těchto vložených objektů.
// Objective C -- příklad 5
//
// ukázka skládání objektů a dynamického rozpoznávání zpráv
// pro simulaci vícenásobné dědičnosti
// (tento příklad není úplným programem)
// importujeme interface všech tříd, po kterých chceme dědit
#import "Class_1.h"
...
#import "Class_N.h"
| #define NCLASSES | N | // počet děděných tříd (pro jednoduchost statický) |
@interface MultipleInheritance: Object
{
| id class[NCLASSES]; | // pole vložených objektů |
}
// předpokládejme, že nová třída nemá žádné 'vlastní' metody, pouze ty zděděné
@end
@implementation MultipleInheritance
// musíme reimplementovat metodu init pro inicializaci vložených objektů
-init
{
class[0]=[Class_1 new];
...
class[N-1]=[Class_N new];
return self;
}
// analogicky musíme reimplementovat metodu free
-free
{
int i;
// zde můžeme snadno určit pořadí uvolňování objektů
for (i=0;i<NCLASSES;i++)
[class[i] free];
return [self free];
}
-forward:(SEL)msg :(marg_list)parms
{
int i;
for (i=0;i<NCLASSES;i++)
// je-li vložený objekt schopen zprávu zpracovat ...
if ([class[i] respondsTo:msg])
// ... předáme mu ji!
return [class[i] performv:msg:parms];
// žádný z vložených objektů zprávu nemůže zpracovat ...
// mohli bychom třeba ohlásit chybu, nebo zprávu ignorovat:
return self;
}
@end
// end of file
1. Interface
Properties objektu obsahují pole odkazů na vnořené objekty. Stejně jako v případě skutečné vícenásobné dědičnosti je zde pole statické; povšimněme si však, že bychom mohli snadno využít dynamické pole, alokované při inicializaci objektu -- tedy něco, co již vícenásobná dědičnost není schopna zajistit.
Nedefinujeme žádné metody, protože objekt žádné vlastní metody neimplementuje: reimplementované metody 'init' a 'free' dědí po třídě Object a všechny ostatní metody přesměruje na vložené objekty.
2. Inicializace
V rámci metody 'init' prostě vytvoříme všechny vložené objekty a jejich identifikace uložíme do pole 'class'. Uvědomme si, že můžeme libovolně změnit pořadí vytváření (a tedy i inicializace) jednotlivých vložených objektů a že můžeme při vytváření kteréhokoli z nich určit jakékoli potřebné parametry -- i to bývá při využití klasické vícenásobné dědičnosti často problém.
3. Uvolnění objektu
Podobně jako metodu 'init' musíme reimplementovat i metodu 'free' -- ta nejprve uvolní všechny vložené objekty a teprve nakonec voláním '[self free]' objekt uvolní sám sebe. Opět můžeme snadno změnit pořadí uvolňování jednotlivých objektů a/nebo můžeme před uvolněním objektu ještě provést jakoukoli jeho potřebnou 'deinicializaci' (uložení dat do souboru apod.).
4. Dynamické rozpoznání zpráv
Pro dynamické rozpoznání zpráv nabízí Objective C zprávu 'forward::'. Ta funguje jednoduchým způsobem: dostane-li kterýkoli objekt zprávu, kterou není schopen zpracovat, pošle mu runtime systém Objective C automaticky zprávu 'forward::'; v jejím rámci může objekt zprávu zpracovat dynamicky. Zpráva 'forward::' má dva parametry: prvním z nich je selektor předávané zprávy, druhým z nich je ukazatel na její parametry.
Implementace zprávy 'forward::' je v našem případě velmi jednoduchá: postupně se 'optáme' všech vloženým objektů jsou-li schopny zprávu zpracovat -- využijeme k tomu standardní zprávu 'respondsTo:', se kterou jsme se seznámili již v minulém příkladu. Jakmile narazíme na objekt který zprávu zpracovat umí, přesměrujeme ji.
Je zřejmé, že bychom mohli algoritmus vyhledávání objektu, kteýr bude zprávu zpracovávat, libovolně obměňovat: mohli bychom měnit pořadí zkoumaných objektů, mohli bychom např. vždy preferovat objekt, který zpracoval minulou zprávu a podobně.
Poznámka: v OpenStepu jsou možnosti dynamického zpracování zpráv výrazně rozšířeny díky třídě NSInvocation -- viz
odpovídající článek ze série o FoundationKitu.
5. Přesměrování zpráv
Pro přesměrování zpráv slouží standardní zpráva 'performv::'. Jejími parametry je selektor zprávy a seznam parametrů, v přesně stejném formátu v jakém je předává zpráva 'forward::'.