Tulajdonságok és kellékek

BecomeAnXcoder – Tulajdonságok és kellékek

Bevezető

Láttuk, hogy egy objektum lehet látható, mint egy ablak, vagy egy szövegmező, vagy pedig lehet láthatatlan, mint egy tömb, vagy egy vezérlő, amelyik a felhasználói felület műveleteit felügyeli. Tehát akkor mi is valójában az objektum?

Az objektum alapvetően értékeket (változókat) tartalmaz és végrehajt akciókat (metódusokat). Egy objektum adatokat tartalmaz és műveleteket végez velük. Egy objektum képes úgy működni, mint egy kis számítógép: üzeneteket küld és fogad. A programod végül is ezeknek a kis számítógépeknek a hálózata, és ezek együttműködésének köszönhetően kapjuk meg a kívánt eredményt.

Objektum összeállítás

A Cocoa programozónak az a feladata, hogy osztályokat készítsen, amik egy sor további objektumot tartalmaznak (sztringeket, tömböket, szótárakat) és ezek olyan értékeket tárolnak, amelyeket az osztály használ működése közben. Néhány objektum ezek közül egy egyszerű metódus keretében lesz elkészítve, használva, aztán eldobva. Mások végig kísérik az objektum életét. Ez utóbbiak a példányváltozók (instance variable), vagy az osztály tulajdonságai (property). Létezhetnek olyan metódusok is amelyek ezekkel a váltózókkal dolgoznak.
Ezek összeállítását nevezzük objektum készítésnek (object composition). Az így készített objektumok jellemzően az NSObject-ből öröklődnek.
Például a kalkulátor vezérlő (controller) osztály tartalmazhatja következő példányváltozókat: a gomb objektumok egy tömbje, az eredmény szövegmező változó. Tartalmazhat továbbá metódusokat: szorzás, összeadás, kivonás, osztás és a grafikus felhasználói felületen való megjelenítés.
A példányváltozókat a header fájlban deklaráljuk. A kalkulátor alkalmazás vezérlő osztálya például így nézhet ki:

//[1] 
@interface MyCalculatorController : NSObject 
{
 	//Instance variables (példányváltozók)
 	NSArray * buttons;
 	NSTextField * resultField; 
}
 //Metódusok 
- (NSNumber *)mutiply:(NSNumber *)value;  
- (NSNumber *)add:(NSNumber *)value; 
- (NSNumber *)subtract:(NSNumber *)value; 
- (NSNumber *)divide:(NSNumber *)value; 
@end

Egységbezárás

Az objektum-orientált programozás egyik célja az egységbezárás (encapsulation): minden osztály legyen önálló és újra felhasználható amennyire csak lehet. Ezen kívül, ahogy a 3. fejezetben már beszéltünk róla, a változók rejtettek a ciklusokban, függvényekben, metódusokban. A változók elrejtése az objektumokra is érvényes. Ez azt jelenti, hogy a példányváltozókat más objektumból nem lehet elérni, azokat csak maga a metódus látja.
Nyílván van olyan eset, amikor egy másik objektum változóját kellene módosítani az adott objektumból. Hogyan lehetséges ez?
Vannak metódusok, amelyek kívülről is elérhetők egy objektum számára. Emlékeztetünk, hogy mindössze annyit kell ehhez tenni, hogy küldeni kell egy üzenetet az objektumnak, hogy hajtsa végre azt a metódust. Ez azt jelenti, hogy azzal tehetjük elérhetővé a példányváltozót, hogy készítünk egy metódus párt amelyik eléri és módosítja a példányváltozót. Az ilyen metódusokat együttesen úgy hívjuk, hogy accessor (kiegészítő) metódusok.
A 8. fejezetben találkoztunk a setIntValue: metódussal az NSTextField osztályból. Ez a metódus az intValue ellenpéldánya. Együttesen két accessor metódusát alkotják az NSTextField osztálynak.

Accessor-ok

Nos hogyan néz ki ez a programkódban? Tanulmányozzuk a következő példát.

//[2] 
@interface MyDog : NSObject {
 	NSString * _name;    //[2.2] 
}
 - (NSString *)name; 
- (void)setName:(NSString *)value; 
@end

Ez az osztály interfész definiál egy objektumot: MyDog. A MyDog-ban van egy példányváltozó, egy sztring, aminek a neve: _name [2.2]. Annak érdekében, hogy ki tudjuk olvasni a MyDog _name változójának a tartalmát, vagy meg tudjuk azt változtatni, definiálni kell két accessor metódust: name és setName:.
Ezzel a header fájllal meg is lennénk. Az implementációs fájl pedig a következőképpen néz ki:

//[3] 
@implementation MyDog 
- (NSString *)name {
     return _name;   //[3.3] 
}
 - (void)setName:(NSString *)value {
         _name = value;   //[3.6] 
} 
@end

Az első metódusban [3.3] egyszerűen visszaadjuk a példányváltozó tartalmát. A második metódusban [3.6] beállítjuk a példányváltozó értékét arra, amit átküldtünk neki. Megjegyzendő, hogy az egyszerűbb érthetőség érdekében leegyszerűsítettük az implementációt. Általában szükség van némi memória kezelés beállításra is ezekben a metódusokban. A következő példa egy sokkal valósághűbb beállítását mutatja az accessor-oknak:

//[4] 
@implementation MyDog 
- (NSString *)name {
     return [[_name retain] autorelease]; 
}
 - (void)setName:(NSString *)value {
     if (_name != value) {
         [_name release];
         _name = [value copy];
     }
 }
 @end

Nem megyünk most bele az extra részek mélységeibe (majd a 15. fejezetben lesz erről szó), de első pillantásra látható, hogy ugyanaz a keret, mint a [3]-as példában, csupán egy kis másolás, megörzés és felszabadítás az, amivel bűvült. A különböző adattípusok különböző memória kezelést igényelnek. (Ezen kívül még meg kell itt jegyezni, hogy nem tanácsos tabulátor jellel kezdeni egy példányváltozó nevét, amit itt most megtettünk az egyszerűbb érthetőség érdekében. A saját programkódodban nyugodtan meghívhatod a “name” változót. Mivel a metódusok és a változók nevei más-más névterületen vannak tárolva, ez nem fog konfliktust okozni.)

Tulajdonságok

A Leopard és az Objective C 2.0 új nyelvi jellemzőket vezetett be a gazdaságosabb programozhatósági feltételek érdekében. Ez pedig nem más, mint a tulajdonságok összeadhatósága. Az accessor-ok közös tulajdonságait kihasználva jelentősen le lehet rövidíteni a programkódot. Kevesebb programkód pedig gyorsabb hibakeresést jelent. :)

Tehát miben különböznek a tulajdonságok az accessor-októl?
Alapvetően, a tulajdonságok közvetlenül szintetizálják az accessor-okat azáltal, hogy a leghatékonyabb memória kezelést használják. Más szavakkal mondva, megírják számodra az accessor metódusokat, de a háttérben, ami azt jelenti, hogy neked soha sem kell a kóddal foglalkozni.
A fenti [2]-es példát Objective-C 2.0-ben a következőképpen írhatjuk meg:

//[5] 
@interface MyDog : NSObject {
 	NSString * name; 
} 
@property (copy) NSString *name; 
@end

Az implementációs fájl pedig így néz ki:

//[6] 
@implementation MyDog 
@synthesize name; 
@end

Ez logikailag ekvivalens a [4]-gyel. Ahogy látszik, kicsit leegyszerűsödött a programkód. Amennyiben az osztályod sok példányváltozója igényel accessor-t, el tudod képzelni mennyire leegyszerűsíti ez az életedet!

eredeti oldal