Les classes cluster són un patró de disseny que fa que la framework Foundation se'n pugui estendre el seu ús. Les classes cluster agrupen un nombre de sub-classes privades i concretes sota una super-classe abstracta i pública. Les classes cluster és la forma per simplificar l'arquitectura pública i visible d'una framework orientada a objectes sense reduint-ne la seva rica funcionalitat.
Per il·lustrar l'arquitectura de les classes cluster i els seus beneficis, considereu una jerarquia de classes que defineix objectes per emmagatzemar números de tipus diferents (chars, ints, floats, doubles). A partir de números de diferents tipus tenen moltes característiques en comú (poden convertir-se d'un tipus a un altre i poden representar-se com strings, per exemple). Podrien representar-se per una única classe. Encara que el seu emmagatzemament té diferents requeriments, seria ineficient representar-los tots per la mateixa classe. Això suggereix la següent arquitectura:
Figura 1: Una Jerarquia Simple de Classes Número
Number és una super-classe abstracta que declara en els seus mètodes les operacions més comunes a les seves sub-classes. Així, no declara una variable d'instància per guardar un número. Els sub-classes declaren com les variables d'instància i comparteixen la interfície programada declarada per Number.
Fins ara, el disseny és relativament simple. Tanmateix, si es prenen en compte els diferents tipus bàsics de C, el diagrama s'assemblaria més a això:
Figura 2: Una Jerarquia de la Classe Number Més Completa
El concepte simple -- crear una classes per mantenir els valors dels números -- poden fàcilment brotar dotzenes de classes. L'arquitectura de les classes cluster presenten un disseny que reflexa la simplicitat del concepte.
Aplicant el patró de disseny de les classes cluster a aquest problema mantindrem la següent jerarquia (les classes privades estan en gris):
Figura 3: L'Arquitectura de les Classes Cluster Aplicada a les Classes Number
Els usuaris d'aquesta jerarquia només veuen un única classes pública, Number, així és possible assignar instàncies del sub-classe adequada? La resposta és la forma en que la super-classe abstracta realitza la instanciació.
La super-classe abstracta en una classe cluster han de declarar mètodes per crear instàncies de les seves subclasses privades. És responsabilitat de la super-classe retornar un objecte de la sub-classe apropiada basada en el mètode de creació que invoques -- no pots escollir la classe de la instància.
En la framework Foundation, normalment crees un objecte invocant un mètode + nomClass... o els mètodes alloc... i init.... Agafant com a exemple la classe NSNumber de de la Framework Foundation, hauries d'enviar aquestes classes per crear objectes number:
NSNumber *unChar = [NSNumber numberWithChar:'a']; NSNumber *unInt = [NSNumber numberWithInt:1]; NSNumber *unFloat = [NSNumber numberWithFloat:1.0]; NSNumber *unDouble = [NSNumber numberWithDouble:1.0];
(Aquesta estil d'instanciació crea objectes que poden des-assignar-se automàticament -- Mireu "Propietat de l'Objecte i Disposició Automàtica" per a més informació. Moltes classes també proporcionen el mètodes estàndard alloc... i init... per crear objectes que necessiten gestionar el seu des-assignament).
Cada objecte retornat -- unChar, unInt, unFloat i unDouble -- poden pertanyer a diferents sub-classes privades. Encara que s'amaga cada tipus de classe de l'objecte, la seva interfície és pública, sent la interfície declarada per la super-classe abstracta, NSNumber. Encara que no és del tot correcte, és convenient considerar els objectes unChar, unInt, unFloat i unDouble com a instàncies de la classes NSNumber, doncs, s'han creat pels mètodes de la classe NSNumber i s'accedeixen a través dels mètodes d'instància declarats per NSNumber.
En l'exemple de sobre, una classe pública abstracta declara la interfície per a múltiples sub-classes privades. Això és una classe cluster en el més pur sentit. També és possible, i sovint desitjable, tenir dos (o possiblement més) classes públiques abstractes que declarin la interfície amb el cluster. Això és evident en la Framework Foundation, el qual inclouen aquests cluster:
| Classe Cluster | Super-classes Públiques |
|---|---|
| NSData | NSData |
| NSMutableData | |
| NSArray | NSArray |
| NSMutableArray | |
| NSDictionary | NSDictionary |
| NSMutableDictionary | |
| NSString | NSString |
| NSMutableString |
També existeixen altres clusters d'aquest tipus, però aquests il·lustren clarament com dos nodes abstractes cooperen declarant les interfícies programades d'una classe cluster. En cadascun d'aquests clusters, un node públic declara mètodes que tots els objectes cluster poden respondre-hi, i pels altres nodes declaren mètodes que només són apropiats pels objectes cluster que permeten modificar els seus continguts.
Aquesta factorització de la interfície dels clusters ajuden a fer més expressiva la interfície programada de les frameworks orientades a objectes:
- (NSString *)titol;
L'objecte llibre retorna la seva pròpia variable d'instància per crear un nou objecte string i retorna aquest -- no importa. Està clar que aquesta declaració que l'string retornar no pot modificar-se. Qualsevol intent per modificar l'objecte retornat causarà una advertència del compilador.
L'arquitectura de classes cluster inclou simplicitat i extensibilitat: Tenint unes poques classes públiques en una multitud de privades fa més senzill aprendre l'ús de les classes en una framework, i quelcom més difícil crear sub-classes dins de qualsevol cluster. Tanmateix, pocs cops serà necessari crear una sub-classe, llavors l'arquitectura de classes és clarament beneficiosa. Els clusters s'utilitzen en la Framework Foundation en només aquestes situacions.
Si trobes que un cluster no proporciona la funcionalitat que el teu programa necessita, llavors, una subclasse pot ser adient. Per exemple, imagina que vols crear una matriu d'objectes en que l'emmagatzemament es basa en fitxers més que en memòria, com en una classe cluster NSArray. Des de que vols canviar el mecanisme d'emmagatzematge de la classes, hauries de crear una subclasse.
Per una altra banda, en alguns casos podria ser suficient (i més fàcil) definir una classe que hi inclou un objecte del cluster. Diguem que el teu programa necessita alertar si es modifica alguna informació. En aquest cas, creant una simple coberta per les dades de l'objecte que defineix la framework Foundation pot ser la millor aproximació. Un objecte d'aquesta classe podria intervenir en missatges que modifiquin les dades, interceptant els missatges, actuant sobre ells i després re-enviant-los a l'objecte amb les dades incorporades.
En resum, si necessites gestionar el teu emmagatzemament de l'objecte, crea una sub-classe real. Altrament, crea un objecte composat, un que inclogui un objecte de la Framework Foundation en un objecte del teu propi disseny. Les seccions següents ofereixen més detall d'aquestes dues aproximacions.
Una nova classe que crees dins una classe cluster ha de:
Des que la super-classe abstracta del cluster és la única públicament visible en la jerarquia del cluster, el primer punt es obvi. Aquest implica que la nova sub-classe ha d'heretar la interfície del cluster però no les variables d'instància, doncs la super-classe abstracta no les declara. Aquest segon punt: La sub-classe ha de declarar qualsevol variable d'instància que necessiti. Finalment, la sub-classe ha de sobre-escriure qualsevol mètode que hereta que directament accedeix a les variables d'instància de l'objecte. Aquests mètodes s'anomenen mètodes primitius.
Un mètode primitiu de classe des de la base per la seva interfície. Per exemple, agafem la classe NSArray, el qual declara la interfície a objecte que gestionen matrius d'objectes. Com a concepte, una matriu guarda un nombre de dades, cadascun dels quals és accessible per l'índex. L'NSArray expressa aquesta noció abstracta a través dels seus dos mètodes primitius, count i objectAtIndex:. Amb aquests mètodes com a base, altres mètodes -- mètodes derivats -- poden implementar-se, per exemple:
| Mètodes Derivats | Possible implementació |
|---|---|
| lastObject | Troba l'últim objecte enviant a l'objecte array aquest missatge: [self objectAtIndex: [self count] -1]. |
| containsObject | Troba un objecte mimtjançant repetidament enviar a l'objecte array un missatge objectAtIndex:, cada cop incrementant l'índex fins que tots els objectes de l'array siguin comprovats. |
La divisió d'una interfície entre mètodes primitius i derivats es fa fàcilment creant sub-classes. La teva sub-classe ha de sobre-escriure les primitives heretades, doncs havent-ho fet així pots estar segur que tots els mètodes derivats que hereta operaran correctament.
Les diferències entre primitives i derivades s'apliquen a la interfície d'un objecte completament inicialitzat. La qüestió de com els mètodes init... podria ser capturat en una sub-classe també es tractat.
En general, una super-classe abstracta de cluster declara un nombre de mètodes init... i + nomClasse. Com s'explica a "Creant Instàncies", la classe abstracta decideix quines sub-classes concretes a instanciar es basen en la teva decisió d'escollir el mètode init... o + nomClasse. Pots considerar que les classes abstractes declaren aquests mètodes per conveniència de la sub-classe. Com que la classe abstracta no té variables d'instància no te necessitat de tenir mètodes d'inicialització.
La teva sub-classe ha de declarar els seus propis mètodes init... (si necessita inicialitzar les seves variables d'instància) i possiblement + nomClasse. Hauria d'invocar els seus inicialitzadors de super-classe dins del seus propi mètode inicialitzador. Dins un cluster de classes, l'inicialitzador designat de la super-classe abstracta sempre és init.
Un exemple ajudarà a clarificar aquesta explicació confusa. Diguem que vols crear una sub-classe de NSArray, anomenada MesArray, que retorna el nom del mes indicant la posició de l'índex. Tanmateix, un objecte MesArray no guardarà la matriu dels noms dels mesos com una variable d'instància. En comptes d'això, el mètode que retorna el nom (objectAtIndex:) retornarà strings constants. Així, només s'assignaran dotze objectes string, i no importa quants objectes MesArray existeixin a l'aplicació.
La classe MesArray es declara com:
#import <foundation/foundation.h>
@interface MesArray : NSArray
{
}
+ mesArray;
- (unsigned)count;
- objectAtIndex:(unsigned)index;
@end
Fixeu-vos que la classe MesArray declara un mètode init... doncs no té variables d'instància que inicialitzar. Els mètode count i objectAtIndex: simplement cobreixen les mètodes primitius heretats, com es veu a sobre.
La implementació de la classe MesArray és com segueix:
#import "MesArray.h"
@implementation MesArray
static MesArray *mesArrayCompartit = nil;
static NSString *mesos[] = { @"Gener", @"Febrer", @"Març",
@"Abril", @"Maig", @"Juny", @"Juliol", @"gost", @"Setembre",
@"Octubre", @"Novembre", @"Desembre" };
+ mesArray
{
if (! mesArrayCompartit) {
mesArrayCompartit = [[MesArray alloc] init];
}
return mesArrayCompartit;
}
- (unsigned)count
{
return 12;
}
- objectAtIndex:(unsigned)index
{
if (index >= [self count])
[NSException raise:NSRangeException format:@"***%s: index
(%d) beyond bounds (%d)", sel_getName(_cmd), index,
[self count] - 1];
else
return mesos[index];
}
@end
Com que el MesArray sobre-escriu els mètodes primitius heretats, els mètodes derivats que hereta treballaran correctament a l'estar sobre-escrits. Els mètodes d'NSArray lastObject, containsObject:, sortedArrayUsingSelector:, objectEnumerator, i altres mètodes treballen sense problemes amb els objectes MesArray.
Per incloure un objecte cluster privat en un objecte de disseny propi, crees un objecte compost. Aquest objecte compost pot confiar en l'objecte cluster per la seva funcionalitat bàsica, només interceptant els missatges que vol tractar d'una forma en particular. Utilitzant aquesta aproximació redueixen la quantitat de codi que has d'escriure i et permet avançar-te al codi comprovat proporcionat per la Framework Foundation.
Un objecte compost pot veure's aquí:
Figura 4: Incloent-hi un Objecte Cluster
L'objecte compost s'ha de declarar a ell mateix per ser una sub-classe del node abstracte del cluster. Com una sub-classe, ha de sobre-escriure els mètodes primitius de la super-classe. També pot sobre-escriure mètodes derivats, però no es necessari que els mètodes derivats treballin a través de les primitives.
Utilitzant com a exemple el mètode count de l'NSArray, la intervenció de la implementació de l'objecte d'un mètode, aquesta sobre-escriptura pot ser tant simple com:
- (unsigned)count
{
return [objecteIncrustat count];
}
Tanmateix, el teu objecte podria posar codi per els seus propis propòsits en la implementació de qualsevol mètode que sobre-escriguis.
Per il·lustar l'ús d'un objecte compost, imagina que vols un objecte de matriu mutables que comprovi els canvis sota algun criteri de validació abans de permetre qualsevol modificació del contingut de l'array. L'exemple següent descriu una classe anomenada ValidantArray, que conté un objecte estàndard de matriu mutable. ValidantArray sobre-escriu tots els mètodes primitius declarats en la seva super-classe, NSArray i NSMutableArray. També declara els mètodes array, validantArray i init, que poden utilitzar-se per crear i inicialitzar una instància:
#import <foundation/foundation.h>
@interface ValidantArray : NSMutableArray
{
NSMutableArray *arrayIncrustat;
}
+ validatingArray;
- init;
- (unsigned)count;
- objectAtIndex:(unsigned)index;
- (void)addObject:object;
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
- (void)removeLastObject;
- (void)insertObject:object atIndex:(unsigned)index;
- (void)removeObjectAtIndex:(unsigned)index;
@end
El fitxer de la implementació mostra com, en el mètode init de ValidantArray, l'objecte incrustat és creat i assignat a la variable arrayIncrustat. Els missatges que simplement accedeixen a l'array i no modifiquen el seu contingut són confiats a l'objecte incrustat. Els missatges que podrien canviar el contingut són escrutinats (aquí en pseudo-codi) i confiats només si passen un hipotètic test de validació.
#import "ValidantArray.h"
@implementation ValidantArray
- init
{
self = [super init];
if (self) {
arrayIncrustat = [[NSMutableArray allocWithZone:[self zone]] init];
}
return self;
}
+ validantArray
{
return [[[self alloc] init] autorelease];
}
- (unsigned)count
{
return [arrayIncrustat count];
}
- objectAtIndex:(unsigned)index
{
return [arrayIncrustat objectAtIndex:index];
}
- (void)addObject:object
{
if (/* és vàlid modificar-ho */) {
[arrayIncrustat addObject:object];
}
}
- (void)replaceObjectAtIndex:(unsigned)index withObject:object;
{
if (/* és vàlid modificar-ho */) {
[arrayIncrustat replaceObjectAtIndex:index withObject:object];
}
}
- (void)removeLastObject;
{
if (/* és vàlid modificar-ho */) {
[arrayIncrustat removeLastObject];
}
}
- (void)insertObject:object atIndex:(unsigned)index;
{
if (/* és vàlid modificar-ho */) {
[arrayIncrustat insertObject:object atIndex:index];
}
}
- (void)removeObjectAtIndex:(unsigned)index;
{
if (/* és vàlid modificar-ho */) {
[arrayIncrustat removeObjectAtIndex:index];
}
}