XVII. Accesseurs et Propriétés▲
XVII-A. Introduction▲
Nous avons vu qu'un objet pouvait être visible, comme une fenêtre ou un champ de texte, ou invisible, comme un tableau, ou un contrôleur qui répond à des actions de l'interface utilisateur. Alors, qu'est-ce qu'un objet exactement ?
Par essence, un objet détient des valeurs (variables) et effectue des actions (méthodes). Un objet contient et transforme à la fois les données. Un objet peut être considéré comme un petit ordinateur en soi, qui envoie des messages et y répond. Votre programme est un réseau de ces petits ordinateurs travaillant tous ensemble pour produire le résultat désiré.
XVII-B. Composition d'objet▲
Le travail d'un programmeur Cocoa est de créer des classes qui contiennent un certain nombre d'autres objets (comme des chaînes, des tableaux et des dictionnaires) pour conserver les valeurs dont la classe aura besoin pour faire son travail. Certains de ces objets seront créés, utilisés et ensuite mis de côté au sein d'une seule méthode. D'autres pourront devoir rester tout le long de la durée de vie de l'objet. Ces derniers objets sont appelés variables d'instance ou propriétés de la classe. La classe peut également définir des méthodes qui marchent sur ces variables.
Cette technique est connue sous le nom de composition d'objet. De tels objets composés héritent généralement directement de NSObject.
Par exemple, une classe contrôleur de calculatrice pourra contenir comme variables d'instance : un tableau d'objets bouton, et une variable champ de texte résultat. Elle pourra aussi inclure des méthodes pour multiplier, additionner, soustraire et diviser des nombres et afficher le résultat dans l'interface graphique.
Les variables d'instance sont déclarées dans le fichier d'en-tête d'interface de la classe. L'exemple de la classe de notre application contrôleur de calculatrice pourrait ressembler à ceci :
//[1]
@interface
MaCalculatriceControleur : NSObject
{
//Instance variables
NSArray
*
boutons;
NSTextField
*
champRésultat;
}
//Methods
-
(
NSNumber
*
)mutiply:(
NSNumber
*
)value;
-
(
NSNumber
*
)add:(
NSNumber
*
)value;
-
(
NSNumber
*
)subtract:(
NSNumber
*
)value;
-
(
NSNumber
*
)divide:(
NSNumber
*
)value;
@end
XVII-C. Encapsulation▲
Un des buts de la programmation orientée-objet, c'est l'encapsulation : rendre chaque classe aussi autonome et réutilisable que possible. Et si vous vous souvenez du chapitre 3, les variables sont protégées de l'extérieur des boucles, des fonctions et des méthodes. La protection de variable est valable aussi pour les objets. Ce que cela signifie, c'est que d'autres objets ne peuvent pas accéder aux variables d'instance à l'intérieur d'un objet, elles ne sont disponibles que pour leurs propres méthodes.
De toute évidence, d'autres objets devront parfois modifier les variables contenues dans un objet. Comment ?
Les méthodes sont disponibles à l'extérieur d'un objet. Rappelons que tout ce que nous avons à faire, c'est d'envoyer un message à notre objet pour déclencher cette méthode. Aussi, le moyen de rendre disponibles des variables d'instance est de créer une paire de méthodes pour accéder à la variable d'instance et la modifier. Les méthodes sont collectivement appelées méthodes accesseur.
Au chapitre 8, nous avons découvert la méthode setIntValue: de NSTextField. Cette méthode est le pendant de la méthode intValue. Ce sont toutes deux des méthodes accesseur de NSTextField.
XVII-D. Accesseurs▲
À quoi cela ressemble-t-il dans le code? Prenons l'exemple suivant.
//[2]
@interface
MonChien : NSObject
{
NSString
*
_nom; //[2.2]
}
-
(
NSString
*
)nom;
-
(
void
)définirNom:(
NSString
*
)value;
@end
Cette interface de classe définit un objet : MonChien. MonChien a une variable d'instance, une chaîne appelée _nom [2.2]. Afin de pouvoir lire _nom de MonChien ou de modifier ce _nom, nous avons défini deux méthodes accesseur, nom et définirNom:.
Jusqu'ici tout va bien. La mise en œuvre ressemble à ça :
//[3]
@implementation
MonChien
-
(
NSString
*
)nom {
return
_nom; //[3.3]
}
-
(
void
)définirNom:(
NSString
*
)value {
_nom =
value; //[3.6]
}
@end
Dans la première méthode [3.3], nous retournons simplement la variable d'instance. Dans la seconde méthode [3,6], nous avons mis la variable d'instance à la valeur passée. Notez que j'ai simplifié cette mise en œuvre pour plus de clarté ; normalement vous devrez traiter tout code de gestion de mémoire nécessaire au sein de ces méthodes. L'exemple suivant [4] montre un ensemble d'accesseur plus réaliste :
//[4]
@implementation
MonChien
-
(
NSString
*
)nom {
return
[[_nom retain
] autorelease];
}
-
(
void
)définirNom:(
NSString
*
)value {
if
(
_nom !=
value) {
[_nom release];
_nom =
[value copy];
}
}
@end
Je ne vais pas entrer dans le détail du code supplémentaire ici (voir chapitre 15), mais en un coup d'œil vous pouvez voir le même schéma qu'en [3], avec juste quelques copying (copier), retaining (conserver) et releasing (libérer) enveloppés dedans. Différents types de valeurs requièrent différents codes de gestion de mémoire (notez aussi qu'en pratique, il est recommandé de ne pas utiliser de caractère de soulignement devant le nom de variable d'instance, j'en ai utilisé un ici pour plus de clarté. Dans votre code, vous pouvez simplement appeler la variable "nom". Puisque les méthodes et les variables ont des espaces de nom distincts, aucun conflit ne surviendra).
XVII-E. Propriétés▲
Léopard et Objective-C 2.0 introduisent de nouvelles fonctionnalités de langage pour traiter plus économiquement ce modèle de programmation. La nouvelle fonctionnalité dont nous parlons est l'ajout de propriétés. Les accesseurs sont si communs que l'aide apportée par cette appréciable nouveauté du langage peut se traduire par beaucoup moins de code. Et moins de code veut dire moins de code à déboguer. :)
Alors, en quoi les propriétés sont-elles différentes des accesseurs ? En gros, les propriétés synthétisent directement les accesseurs, en utilisant la gestion de mémoire la plus efficace et la plus appropriée. En d'autres mots, elles écrivent les méthodes accesseur pour vous, mais en arrière-plan ; donc vous ne verrez même jamais le code.
En reprenant notre exemple [2] ci-dessus, en Objective-C 2.0, nous pourrions écrire à la place :
//[5]
@interface
MonChien : NSObject
{
NSString
*
nom;
}
@property
(
copy) NSString
*
nom;
@end
Et notre mise en œuvre ressemblerait à ça :
//[6]
@implementation
MonChien
@synthesize
nom;
@end
Ce qui est logiquement équivalent à [4]. Comme vous pouvez le voir, cela simplifie quelque peu notre code. Si votre classe contient beaucoup de variables d'instance ayant besoin d'accesseurs, vous pouvez imaginer à quel point votre vie devient plus facile !