

La Programmation : Le Langage Objective-C (2)
La Programmation : Le Langage Objective-C (2)
Sujets abordés :
- Utilisation d'une classe
- Héritage
- Les Protocoles
- Les Catégories
Bonjour à tous et bienvenue dans ce nouveau tutoriel sur la programmation en Objective-C avec l'API Cocoa d'Apple. Tout d'abord merci à tous pour vos encouragements, ça fait plaisir et ça donne envie de continuer . Je vous invite à revoir l'épisode précédent afin d'y prendre en compte les modifications que j'y ai apporté, pour y accéder, vous passez par la section "tutoriaux" dans la partie "La Programmation".
Je suis désolé pour l'attente de quelques mois que je vous ai faite subir, mais je vais chercher à être plus rapide maintenant dans l'écriture de ce tutoriel. Aujourd'hui, nous allons nous attaquer à l'utilisation d'une classe, ainsi que la gestion de la mémoire mais aussi et surtout au concept d'héritage, concept majeur de la POO et donc de l'Objective-C !
La Classe Personnel
Utilisation de notre classe
Alors, pour l'instant notre classe n'est pas très volumineuse, il n'y a pas grand chose dedans ! Mais bon on peut toujours en faire quelque chose. Je précise que je ne ferais pas d'interaction avec l'utilisateur ici (en utilisant printf/scanf), tout sera programmé en dur dans le code, les interactions n'apportent en effet pas grand chose en l'occurrence, du moment que vous connaissez le code et que vous voyez le résultat.
Notre programme se basera donc essentiellement sur la fonction C
andNumTel:
Personnel *p2 = [Personnel personnelWithNom:
andNumTel:
NSLog(
[p3 setNumTel:[p1 numTel]];
NSLog(
[p1 release];
[p2 release];
[p3 release];
Voilà, voilà... Nous commençons notre petit programme par une série de déclaration / allocation / initialisation d'objets. Dans le premier cas nous déclarons un pointeur sur un Personnel, il ne faut pas oublier qu'il faut toujours déclarer des pointeurs sur objet ! Donc nous déclarons p1 de type "Personnel *" et puis nous l'allouons et l'initialisons à l'aide de notre méthode désignée qu'on a vu dans le précédent chapitre. Nous faisons de même pour p2 en utilisant cette fois-ci notre méthode de classe qui fait, je vous le rappelle, l'allocation et l'initialisation elle-même, dans tous les cas cette instruction a le même effet que la précédente (aux arguments près) et nous avons ainsi deux nouveaux "Personnel" ayant chacun des valeurs différentes.
Dans le cas de la troisième instruction, vous remarquerez que j'ai remplacé "Personnel *" par le mot clé "id". Ce mot clé représente en fait un pointeur sur un objet, d'où l'absence de l'étoile, sur un NSObject, c'est-à-dire un objet sans aucune autre spécificité que d'être un objet. Ce mot clé permet de faire du "typage dynamique", il s'agit d'un concept important de l'Objective-C et qui permet de moins s'embêter avec toutes les classes, en fait, on ne précise pas au compilateur quelle classe d'objet sera affectée à cette valeur et ce ne sera que lorsque l'application tournera que le type de l'objet sera connu. Si vous voulez, vous pouvez déclarer des "id" partout sans distinction, à chaque fois que vous avez besoin d'un objet, vous mettez un "id", le problème c'est que vous ne donnez aucune information au compilateur et que si vous envoyez à cet objet un message auquel il n'est pas capable de répondre, vous n'obtiendrez pas le résultat souhaité ce qui peut être dommageable. Si vous êtes sûr du type d'objet que vous souhaitez affecté à votre variable, je ne vous conseillerais que trop de le préciser.
Toujours est-il qu'on affecte à notre variable de type "
"
Dans l'instruction qui suit, on utilise la fonction C "
Justement, dans l'instruction suivante nous utilisons la méthode "
Héritage
Voilà on peut entamer l'héritage, concept très important de l'Objective-C et de la programmation objet en général. L'héritage est le fait de mettre en relation 2 classes, mais d'une façon spécifique, la relation entre super-classe et sous-classe est de type "est un". Prenons un exemple dans la nature. La biologie a cette avantage de présenter un nombre incroyable de classification et sous-classification. On va donc reprendre l'exemple de notre Chat, comme tout le monde le sait, enfin j'espère, le Chat "est un" Félin, tout comme le Tigre ou le Lion, on déjà un début d'arborescence : la racine c'est "Félin" est on a nos branches : "Chat", "Lion", "Tigre". Chacun de ces animaux sont des Félins, c'est-à-dire qu'ils ont des caractères communs tels que la forme du crâne, les pupilles en amande, les griffes rétractiles, etc. Mais bien qu'ils soient de la même famille, on ne peut pas dire que ces Félins sont pareils, ils ont des caractères propres, par exemple le Tigre a le pelage rayé, le Lion a une crinière et le chat miaule et ronronne. Donc l'héritage c'est ça, c'est "factoriser", autrement dit réunir, du code commun à plusieurs classes afin d'en économiser la réécriture, qui peut être fastidieuse à force ! Si on en revient à nos félins, on peut dire sans peine que ce sont des mammifères tout comme les canidés ou les primates, et parmi ces primates on compte les chimpanzés, les singes et les humains. Tout comme le chat, les primates on des mamelles et des poils puisqu'ils sont mammifères, en revanche ce qui les différencient des chats c'est le fait qu'ils ont des ongles et non pas des griffes, ou bien des yeux avec pupille ronde et non en amande. Et puis la différence entre singe et humain c'est qu'on marche généralement sur deux pattes plutôt que 4, etc.
Donc en programmation c'est exactement le même principe, la super-classe permet de partager du code entre ses sous-classes pour ne pas avoir à le réécrire, chacune de ces sous-classes peut être instanciée comme étant un objet de sa super-classe, etc. Mais ne vous inquiétez pas nous allons y revenir. Déjà, ce qu'il faut savoir c'est qu'en Objective-C, comme en Java, l'héritage est simple, c'est-à-dire qu'une classe ne peut avoir qu'une et une seule super-classe, en revanche cette super-classe peut avoir une infinité de sous-classe, on peut remarquer aussi que toutes les classes héritent directement ou indirectement de la classe racine (root class) NSObject, en Java il s'agit de la classe Object, c'est-à-dire que toute classe est un objet et hérite de méthode tel que l'allocation, désallocation, description, release, etc. C'est une grosse différence avec le langage C++ qui lui possède un héritage multiple et pas de super-classe racine cela pose des problèmes lorsqu'il faut par exemple créer des méthodes d'affichage, on est obligés d'utiliser un concept obscure nommé l'amitié... En revanche il peut être utile de donner à des classes différentes dans des branches différentes (c'est-à-dire qui héritent de super-classes différentes) sans aucun caractère en commun, et bien justement, des caractères commun, le Java et l'Objective-C permettent de parer à ce problème grâce au système des interfaces (pour Java) et des protocoles (pour Objective-C). Si une classe ne peut hériter que d'une super-classe, en revanche elle peut hériter d'une infinité de protocole. Mais on parlera de ça plus loin de ça, ça peut-être utile dans de grosses arborescences.
Alors je vous le dis tout de suite, je suis assez flemmard mais aussi l'écriture de ce qui suit dans le programme est plutôt fastidieuse, alors au lieu de vous écrire tout ce qu'il faut mettre je vais vous donner les fichiers à télécharger pour les ajouter le plus simplement du monde à votre projet. Nous allons y ajouter 3 nouvelles classes pour vous exposer concrètement la notion d'héritage. Voici donc, un petit diagramme de classes généré à l'aide d'Xcode :
Ceci est un diagramme UML plus ou moins formalisé. Dans le cartouche du rectangle vous avez le nom de la classe, puis en-dessous vous avez deux espaces, nommés ici : Properties (propriétés) et indiquant tous les attributs de votre classe suivi de leur type, puis le champ Operations (opérations) contenant toutes les méthodes de votre classe, les méthodes non-soulignées sont les méthodes d'instance et les méthodes soulignées sont les méthodes de classe. Ce que ce diagramme nous montre de très intéressant, ce sont les flèches, les flèches qui indiquent quelle classe hérite de quelle classe, en l'occurrence 3 des classes héritent de Personnel, directement ou indirectement, et Personnel hérite de NSObject qui n'est pas indiquée ici.
Maintenant, je vous propose de télécharger ce petit fichier au format ".SITX" : tutoriel.sitx pour télécharger appuyez sur la touche "alt" en cliquant sur le lien.
Le dossier contient 10 fichiers :
- En-Tete.h : Il permettra de simplifier l'importation de tous les fichiers d'en-tête de classes.
- Personnel.h : Fichier d'en-tête de la classe Personnel
- Personnel.m : Fichier source de la classe Personnel
- Employe.h : Fichier d'en-tête de la classe Employe
- Employe.m : Fichier source de la classe Employe
- Directeur.h : Fichier d'en-tête de la classe Directeur
- Directeur.m : Fichier source de la classe Directeur
- Commercial.h : Fichier d'en-tête de la classe Commercial
- Commercial.m : Fichier source de la classe Commercial
- main.m : Contient un petit programme utilisant les classes.
Contrairement à ce qui serait souhaitable, aucun de ces fichiers n'est commenté ! Mais bon en l'occurrence ce n'est pas grave puisque je vais tout vous expliquer en détail dans ce tutoriel.
Pour commencer, le premier fichier n'est pas particulièrement compliqué, d'ailleurs il ne s'agit pas d'une classe mais juste d'un fichier permettant de réunir les "
Alors voilà, maintenant on passe aux deux fichiers suivants concernant notre classe "Personnel". Cette petite classe a subit pas mal de modifications depuis la diffusion du premier épisode à cause de (ou grâce à???) mes diverses recherches sur l'Objective-C qui m'ont permises d'améliorer son code.
Vous pouvez remarquer l'apparition de la méthode de classe "
En ce qui concerne le fichier source, la première modification est la variable " ) les objets partageront la même variable avec la même valeur. Ici, nous voyons qu'elle n'est pas initialisée c'est quelque chose que nous allons faire dans notre fameuse méthode
"
En effet, si seul Personnel implémente la méthode et que ses sous-classes ne l'implémentent pas alors à chaque fois qu'on créera le premier objet d'une des sous-classes, la variable static sera remise à zéro à chaque fois, et ça ce n'est pas bon, on peut se retrouver avec plusieurs personnes avec le même numéro... Alors la solution est d'utiliser l'une des méthodes de classe de NSObject : "
Cela donne le code suivant :
Ainsi, tout le code contenu entre les accolades ne sera exécuté que si le receveur est Personnel donc une seule et unique fois. Voilà pour le reste il n'y a pas de grande difficulté. Mise à part la gestion des copies dans notre méthode "setNumTel:", je vous parlerai plus en détail de cela plus tard. En effet, en Objective-C, il est préférable lorsqu'on affecte un objet à une variable d'instance de ne pas faire de copie, c'est-à-dire réserver une nouvelle zone mémoire et y placer les mêmes valeurs. Au lieu de ça, on relie la variable d'instance (la variable de l'objet) à l'adresse de l'objet que l'on veut lui affecter, cela permet des économies.
Maintenant, regardons notre petite calculPaie: en fait elle n'est là que pour cause de polymorphisme. Le polymorphisme est l'un des concepts de la programmation objet, il s'agit de la capacité d'une méthode à donner des réponses différentes selon le type de l'objet sur lequel la méthode est appelée. Imaginons la méthode "lancer", je suis pas sûr qu'une balle de golf et un chat réponde de la même manière à une telle méthode . C'est de ça qu'il s'agit. Deux classes peuvent implémenter une méthode portant le même nom, mais donner un résultat différent. Ici, cette méthode retourne pratiquement rien (zéro c'est pas grand chose), mais c'est juste parce que le compilateur, contrairement au C++ et au Java, vous renvoie un avertissement (permet l'exécution mais peut provoquer des bogues) si une méthode est déclarée dans l'en-tête mais qu'elle n'est pas implémentée ou qu'elle ne retourne pas la valeur souhaitée. Vous verrez plus tard comment écrire de façon plus élégante et compréhensible en Objective-C
.
Les Sous-Classes Employe et Directeur
À présent passons aux classes Employe et Directeur. Dans la première partie, vous pouvez voir leur position dans l'arbre, elles se trouvent au même niveau et possède chacune deux fichiers. Commençons par la désormais coutumière
Ces petites classes héritent de Personnel, c'est donc tout naturellement que les deux points suivant leur noms sont eux-même suivi de "Personnel" car tout employé et tout Directeur est un Personnel. Après cela peu de difficulté, vous avez deux nouvelles variables de classe pour le calcul du salaire, elles possèdent ensuite leurs accesseurs correspondant, notre méthode calculPaie: qui servira à donner le salaire de l'employé ou du directeur et la méthode de désallocation de la mémoire. Petite nouveauté pour les initialiseurs, déjà le nom de la méthode de classe de création d'un employé et d'un directeur a changé, mais aussi les 4 initialiseurs ont 2 paramètres en plus... En fait, dans le cas de nos employés et de nos directeurs, lors de la création de l'objet, il faut entrer les informations requises pour les employés et les directeurs ainsi que celles requises pour les personnels, vu que l'initialiseur désigné de la classe Personnel les demande, c'est pour ça que nous avons autant de paramètres, d'où l'intérêt de les nommer explicitement. Je limite le nombre d'initialiseur, on a déjà vu comment on faisait. Voilà pour les fichiers d'en-tête. Si vous avez des problèmes à ce niveau... Inquiétez-vous car il n'y a rien de vraiment nouveau . Vous remarquerez aussi que je n'ai pas mis de méthode
On passe maintenant aux fichiers sources, c'est-à-dire la partie . Et pour en finir avec les codes sources de ces classes, vous voyez que
La Sous-Classe Commercial
Voilà, nous pouvons passer au dernier niveau de notre arbre, la classe Commercial. Cette classe est très simple aussi, et c'est encore des répétitions. Vous remarquez que les initialiseurs d'instance ont maintenant 6 paramètres et ça commence à faire vraiment beaucoup, mais vous pouvez créer vos objets de façons différentes, par exemple mettre le salaire grâce à une autre méthode et mettre une valeur par défaut avec l'initialiseur. Vous remarquez à nouveau que les méthodes
Encore une fois je passe vite sur tous ces détails car il y a pas de nouveauté par rapport à précédemment.
Utilisation de nos classes
Vous pouvez voir dans le fichier main.m une manière d'utiliser les classes que nous avons créé, il vous suffira de copier coller le code situé dans le main, dans le main de votre code pour voir le résultat. Vous remarquerez alors que les descriptions de chaque personnel change selon le type de même pour la calcul de la paye, c'est signe que le polymorphisme fonctionne correctement !
Les @protocol ou le pseudo-multi-héritage
Dans la partie précédentz, je vous avais dit que, contrairement au C++, le java et l'objective-c n'utilise que l'héritage simple, c'est-à-dire qu'une classe ne peut hérité que d'une seule super-classe. Cependant, plusieurs classes ne se trouvant pas dans les mêmes branches de l'arborescences peuvent avoir besoin de réagir aux mêmes méthodes. Dans notre cas, nous pouvons voir la méthode "calculPaie", auquel chacune de nos classes doivent pouvoir répondre, sauf la classe Personnel, pour laquelle ce n'est pas logique de calculer la paye puisqu'il n'y aucune valeur à retourner. Nous utilisons donc les protocoles en Objective-C pour permettre ce tour de passe-passe.
Déclaration
Pour déclarer un protocole, c'est-à-dire finalement le créer, rien de plus simple, on le déclare comme l'interface d'une classe sans les accolades et on remplace
Étant donné qu'il doit être déclaré dans un en-tête et puis être importé par les classes qui l'utilise... Autant déclarer le protocole à la suite de la déclaration de la classe Personnel, comme ça on est sûr que les autres classes l'auront. Voilà notre petit protocole :
Vous remarquez déjà qu'il n'y a pas de classe ou de protocole parent, en effet il n'y a pas de hiérarchie parmi les protocoles, vous pouvez en faire une si vous avez besoin mais aucune obligation (en lui faisant implémenté des protocoles). Le nom d'un protocole répond aux mêmes conventions que les noms de classes, essayez de donner un nom explicite.
Implémentation
Pour qu'une classe implémente un protocole, on dit qu'une classe se conforme à un protocole, il suffit d'ajouter sur la ligne @interface, avant l'accolade, ceci :
Une classe peut implémenter plusieurs protocoles, il suffit de les séparer par des virgules entre les chevrons (
Finalement, vous devez faire ces changements :
- Écrire le protocole Paiement à la suite de l'
@interface de la classe Personnel - Supprimer la déclaration et l'implémentation de la méthode "
calculPaie " de la classe Personnel - Supprimer la déclaration et non l'implémenation de la méthode "calculPaie" des classes Employe, Directeur et Commercial
- Ajouter "
" sur la ligne @interface de Employe, Directeur et Commercial
Vous pouvez ensuite exécuter votre programme une fois de plus et vous ne remarquerez aucun changement, ce qui est normal, nous avons fait que des modifications syntaxiques et non sémantiques. Vous pouvez cependant voir l'intérêt d'un protocole pour faire partager des actions portant le même nom à des classes différentes et sans rapport entre elles.
Les catégories : Extensions de classe
Autant le concept des protocoles existe dans Java (sous forme d'interface) autant celui des catégories ne se trouve que dans l'Objective-C. Ce système permet d'étendre les classes déjà créées sans avoir même besoin de connaître le code de la classe originelle. Attention vous ne pouvez qu'ajoutez des méthodes et pas des variables d'instances, il est aussi possible de masquer les méthodes de la classe par des méthodes de même nom dans la catégorie. Une fois la catégorie créée, elle fait partie intégrante de la classe, c'est-à-dire que le compilateur s'attend à ce que tous les objets de cette classe répondent aux méthodes de la classe ainsi que celles de ses catégories. Les méthodes d'une catégorie ont aussi accès à toutes les variables d'instance de la classe qu'elles étendent, y compris les variables déclarées comme @private. Il n'est d'ailleurs pas nécessaire de connaître le code source de la classe à étendre pour faire une catégorie, vous pouvez aussi bien étendre une classe conçue par NeXTSTEP (comme NSArray). Mais ce système est aussi très utilisé par Apple pour classer les méthodes, en effet, si vous regarder les fichiers d'en-tête contenu dans la framework Foundation, vous pourrez voir des tas de catégories. Il n'est pas rare de voir définies dans l'interface de la classe quelques méthodes de bases puis dans d'autres catégories des méthodes regroupées par thèmes. Cela rendra votre code plus clair.
Mais sinon, comment définit-on une catégorie ? Une catégorie ressemble beaucoup à une classe, seulement il n'y a pas à mettre la super-classe et pas d'accolade. Sinon vous définissez l'interface dans un fichier d'en-tête et l'implémentation dans un fichier source tout comme les classes.
Voici la syntaxe dans le fichier d'en-tête (.h) :
Voici la syntaxe dans le fichier source (.m) :
Rien de plus simple ! Donc dans notre exemple, puisque je vais vous en faire un petit quand même, on va mettre la catégorie dans les fichiers de la classe qu'elle étend pas besoin d'en faire plus. Nous allons créer une catégorie pour la classe Directeur et ajouté deux petites méthodes histoire de faire la démonstration.
Voici ce que vous pouvez ajouter à la suite de l'interface de la classe Directeur :
- (NSString*) description;
- (NSString*) licenciement;
Ici, nous masquons la méthode description par une nouvelle version. Notez que si vous faites plusieurs catégories avec plusieurs versions d'une même méthode le comportement sera alors indéfini. Et voilà ce qu'on va rajouter à la suite de son implémentation :
- (NSString*) description
{
Telephone : %@\n
Indemnites : %.2f\n
Prime : %.2f\n
Remuneration : %.2f\n",
nomPers, (numTel ? numTel : @"Aucun"),
indemnites,prime,[self calculPaie]];
}
- (NSString*) licenciement
{
licenciement : %.2f"
}
Vous pouvez tenter la compilation vous verrez les modifications que l'on a apporté grâce à la catégorie ! Vous pouvez de même tester la nouvelle méthode grâce au message : "
Voici donc ce qui clôt ce deuxième chapitre sur l'Objective-C qui, je suis désolé, a mis beaucoup de temps à venir. Il est assez lourd, il y a beaucoup de choses, mais peu de difficultés, je vais tout de même vous faire un résumé dans le chapitre suivant qui prendra, je vous le promets, moins de temps à venir.
Si vous avez des questions, des suggestions, des commentaires, vous pouvez me contacter par les voies habituelles : section Contacter, Forum, ...
Psycho
Excellente 2eme partie de ce tuto. Merci pour tes efforts continus, PsychoH13. Petite remarque: il y a des petits erreur pour l'implementation de la categorie Paiement (eg le positionnement et la syntaxe du sur la ligne @interface n'est pas correcte). J'attends avec impatience la suite de ce tuto qui pourra bien devenir la reference pour l'objective C! :)