2.2 La définition de nouvelles méthodes
Une fois ces quelques classes définies, nous devons donner des savoir-faire aux
instances.
Tout d’abord notons que nous avons créé la classe Animal exclusivement pour
avoir une sur-classe pour toutes les classes d’animaux que nous voulons créer par
la suite. Nous n’avons nulle intention de créer des instances de cette classe.
Qu’est-ce que serait une instance de la classe Animal? Un chien? Une baleine?
Quoi? C’est comme dans l’arbre philogénétique: les différentes classes abstraites,
telles que “vertébrés”, “mammifères” ou encore “euthériens” n’existent que pour
grouper ensemble des caractéristiques partagées par toutes les sous-classes. Si l’on
parcourt l’arbre du haut vers le bas, on va du général vers le particulier: tous les
animaux ont de l’ADN, avoir de l’ADN est alors une propriété qui est définie au
niveau de la classe abstraite Animal. Seuls les mammifères allaitent leurs
petits, c’est donc une caractéristique qui sera définie au niveau de la
classe “mammifère” et partagée par toutes ses sous-classes possibles, les
marsupiens aussi bien que les ongulés, les chiens, les primates ou encore les
représentants de la classe “homo sapiens”. La classification sert aussi à
factoriser des propriétés et des savoir-faire communs à la classe et toutes ses
sous-classes.
Puisque nos animaux partagent tous la propriété d’avoir un nom, à travers la
variable d’instance nom définie dans la classe abstraite Animal, un savoir-faire que
tous les animaux doivent avoir c’est de répondre par leur nom si on le leur
demande. Ce savoir-faire sera défini dans la classe Animal par la méthode nom
ci-dessous:
nom
"répond avec la valeur de la variable d’instance nom"
^nom
|
|
Expliquons: la première ligne donne le nom de la méthode, nom, ce sera le
sélecteur qui pourra être utilisé ensuite dans des transmissions demandant à un
receveur son nom. La deuxième ligne est un commentaire pour le lecteur de ce
bout de programme. Tout ce qui se trouve entre deux guillemets est commentaire
et ignoré par SMALLTALK (mais pas par nous). La troisième ligne est le corps de
la méthode. Le corps dit ce que le receveur doit faire si on lui envoie le message
nom, le nom de la méthode. Ici, seule l’instruction “^nom” est à exécuter. Le signe
“^”2
dit au receveur de retourner au demandeur, à l’initiateur de la transmission, donc
à l’émetteur du message nom, la valeur de l’expression qui suit ce signe. Ce qui
suit ici c’est le nom de la variable d’instance, nom; en clair, la méthode nom dit
que quand quelqu’un vous demande votre nom, vous répondez avec votre nom.
Faites bien attention aux rôles différents du même mot nom ici. Dans la première
ligne de la définition d’une méthode, le premier mot ne peut qu’être le nom d’un
nouveau sélecteur. Dans le corps de la méthode, dépendant de l’endroit où l’on
trouve un mot, il peut soit désigner un sélecteur, soit une variable d’instance, soit
un autre type de variable (mais cela nous le verrons par la suite). Ici,
dans le corps, il ne peut pas s’agir d’un sélecteur, puisqu’il n’y a pas de
transmission3.
Pratiquement, pour définir une nouvelle méthode nous devrons utiliser
l’explorateur de classes, sélectionner la classe à laquelle on désire ajouter cette
méthode et cliquer dans la sous-fenêtre protocole (la troisième sous-fenêtre de la
première rangée de fenêtres de l’explorateur, cf. page 44). Dans notre cas,
sélectionnons alors la classe Animal. Puisque nous n’avons encore défini aucune
méthode pour cette classe, la sous-fenêtre protocol n’affiche que deux lignes
-all- et no messages. Si on sélectionne la ligne -all-, la sous-fenêtre de
méthodes affiche toutes les méthodes définies pour les instances de la
classe sélectionnée. Ici, clairement, si nous faisons cela, la sous-fenêtre
de méthodes reste désespérément vide: aucune méthode n’a encore été
définie.
Créons d’abord quelques protocoles, un pour les méthodes d’initialisation des
instances, nous l’appelerons initialize, un pour les affaires privées des
instances, nous l’appelerons private, et un dernier, appelé actions, qui devrait
grouper les différentes actions que chaque instance de la classe doit savoir faire.
Allons-y. Choisissons dans le menu contextuel de la sous-fenêtre des protocoles
l’entrée «new category...» et choisissons dans le nouveau menu qui se présente,
l’entrée «new...». Dans la petite fenêtre qui s’ouvre ensuite, donnons le nom de
notre nouveau protocole, private par exemple, cliquons sur accept et nous
verrons alors s’afficher dans la sous-fenêtre des protocoles le nom de ce nouveau
protocole, qui s’ajoute à ceux déjà existants, ou qui remplace la ligne no
messages si aucune méthode n’est déjà définie dans la classe sélectionnée.
Tout cela a l’air très compliqué. Dans la pratique c’est tout simple à
faire, ce n’est que la description détaillée des actions à suivre qui donne
cette impression de complexité (cf. la note de bas de page de la page
5).
Répétons ces opérations pour ajouter aussi les protocoles initialize et
actions. Comme cela nous aurons nos trois protocoles pour les instances de la
classe.
Si nous cliquons maintenant sur le protocole private, la sous-fenêtre
principale affiche un canevas pour l’écriture d’une méthode. Le voici:
message selector and argument names
"comment stating purpose of message"
| temporary variable names |
statements
|
|
Il donne la forme la plus générale de définition d’une méthode. Puisque ce
canevas est sélectionné (en surbrillance), il suffit d’y écrire la méthode que nous
venons de définir ci-dessus. N’oublions pas de terminer avec les caractères
«Alt+s» pour dire à SMALLTALK que nous désirons qu’il accepte cette définition.
Enfin, nous avons défini une première méthode, nom, et nous l’avons bien mis dans
le protocole private. La figure 2.6 montre l’explorateur après toutes ces
opérations.
Continuons notre petit programme. Pour qu’un animal puisse nous livrer son
nom, il faut d’abord le baptiser, lui donner un nom. Donner un nom à un
animal veut dire: donner une (nouvelle) valeur à sa variable d’instance
nom. Clairement, ceci doit être également une méthode définie au niveau
de la classe Animal. Nous allons l’ajouter au protocole initialize. La
voici:
nom: aString
"change le nom de l’animal en aString"
nom := aString
|
|
Explication: le sélecteur de cette méthode s’appelle nom:. Notons que le signe
“:” fait partie du mot. Ce signe indique à SMALLTALK que le sélecteur que nous
sommes en train de définir a besoin d’un argument lors d’une utilisation dans une
transmission.4
Pour se référer à cet argument nous lui donnons le nom aString. aString est le
nom d’un paramètre qui, à l’intérieur du corps fait référence à l’argument donné
lors d’une transmission de ce message. Le nom aString de ce paramètre a été
choisi pour indiquer que l’argument devrait être une chaîne de caractères (en
anglais: “a string”).
Comme dans notre première définition de méthode, la deuxième ligne est un bref
commentaire indiquant le rôle de cette méthode. Et, encore comme dans la méthode
ci-dessus, le corps se réduit à une seule expression. Ici c’est une affectation. Le signe
“:=”5
est le signe d’affectation, indiquant que la variable à sa gauche reçoit comme
nouvelle valeur la valeur de l’expression à droite de ce signe. Ici donc, la variable
d’instance nom recevra comme nouvelle valeur la valeur du paramètre aString,
donc de l’argument transmis lors de la transmission du message nom:. Notons que
cette méthode n’utilise pas le signe retourne (^); elle n’indique donc pas de
manière explicite la valeur qu’il faut retourner à l’émetteur. Dans de tels cas,
SMALLTALK retourne, par défaut, le receveur du message, le receveur
disant ainsi qu’il a bien reçu le message et qu’il a fait tout ce qu’on lui a
demandé.
Finalement, ajoutons aussi une méthode au protocole actions en donnant à
tout animal la possibilité de parler, i.e. de répondre quelque chose quand on lui
demande de dire quelque chose. Bien entendu, a priori aucun animal ne sait
parler. C’est exactement cela que nous allons mettre dans la méthode unaire
parle ci-dessous:
parle
"un message disant que l’animal ne sait pas parler"
self answer: ’je ne sais pas parler’
|
|
Une fois que vous avez entré cette méthode, quand vous tapez
«Alt+s» (ou quand vous choisissez accept dans le menu contextuel)
une fenêtre apparaîtra disant que vous utilisez un sélecteur inconnu:
answer:.6
SMALLTALK a raison: en effet, nous n’avons défini aucune méthode du nom
answer:. La fenêtre en question vous donne aussi tout un choix de noms de
sélecteur connus qui ressemblent au nom que vous avez donné. Le premier de ces
noms est le nom du sélecteur que SMALLTALK n’a pas reconnu. C’est une aide de
SMALLTALK. Chaque fois que vous entrez quelque chose que la machine
ne connaît pas, elle vous en informe, et elle vous propose des écritures
similaires, pour le cas où vous auriez fait une erreur de frappe. Ici, c’est très
intentionnellement que nous avons écrit answer:; nous allons donc confirmer en
cliquant le sélecteur dont SMALLTALK pense qu’éventuellement, vous avez pu vous
tromper, answer:.
Examinons cette méthode un peu plus en détail. Son corps dit:
self answer: ’......’
Nous envoyons un message à self. Qui c’est? self est un mot réservé en
SMALLTALK, on dit que c’est une pseudo-variable(cf. section 2.7, page 119). self
est une manière de pouvoir se référer au receveur de ce message. Vous vous
rappelez? Nous avons dit que l’acte fondamental de la programmation objet est la
transmission de message. Une transmission implique toujours un receveur et un
message (eventuellement des arguments en plus). De temps à autre il est
nécessaire de connaître le receveur d’un message quand on écrit une méthode.
Ceci, tout simplement, puisque la méthode dit au receveur ce qu’il faut faire, et il
peut être nécessaire de lui dire de faire, en plus de ce qui est dit, autre chose
encore. Pour cela, il nous faut bien connaître le receveur. Mais un même
message peut être envoyé à plein d’objets, chacun d’eux se considérant
correctement le receveur de ce message. Pour pouvoir s’adresser à chacun d’eux
individuellement, SMALLTALK nous donne cette pseudo-variable self, qui sera
comprise par chaque receveur comme désignant lui même. C’est un peu
comme si on dit vous. Qui est “vous”? Probablement chaque personne à
qui on dit “vous” est désignée par ce même mot. Ici donc, le corps de
la méthode dit quelque chose comme «si on vous demande de parler,
envoyez à vous-même le message answer: avec l’argument ’je ne sais pas
parler’», ou, encore plus simplement,: «si on vous demande de parler
répondez7
je ne sais pas parler», c’est une chose qui nous arrive régulièrement quand nous
nous trouvons dans un pays où les habitants pratiquent une langue qui nous est
inconnue.
Bon, tout ce qui reste à faire c’est d’enseigner aux animaux comment
répondre, comment answer:. Voici cette dernière méthode d’instance que nous
allons mettre dans le protocole initialize de la classe Animal:
answer: aString
"imprime la classe et le nom de l’animal en question
suivi de l’argument a String"
Transcript show:
(self class asString , ’ ’ ,
self nom , ’: ’ , aString);
cr
|
|
Bien qu’elle soit légèrement plus longue que les trois autres méthodes que
nous avons écrites, elle n’est pas réellement plus difficile à comprendre. Le
Transcript est, comme le Workspace une fenêtre spéciale qu’on peut ouvrir soit
à travers l’entrée open... du menu principal, soit en l’important à travers
l’attache Tools, soit simplement par l’envoi du message unaire open à l’objet
Transcript.8
Cette fenêtre sert, en général, d’écran d’affichage de messages systèmes et, par
extension, elle sert au programmeur SMALLTALK d’écran sur lequel il affiche ses
propres messages. Le Transcript est une fenêtre alpha-numérique, c’est-à-dire
une fenêtre ne sachant afficher que des caractères ordinaires, des lettres et
des chiffres. Pour afficher une chaîne de caractères dans le transcript, il
suffit de lui envoyer le message show: avec comme argument la chaîne
de caractères qu’on veut afficher. Ainsi le fameux programme C Hello
World:
#include <stdio.h>
void main(int argc, char **argv){
printf("Hello World")
}
|
|
s’écrit en SMALLTALK simplement comme:
Transcript show: ’Hello World’
et si l’on veut que l’impression se termine par un retour charriot, un aller à la
ligne, il faut, comme en C, le préciser:
#include <stdio.h>
void main(int argc, char **argv){
printf("Hello World\n")
}
|
|
en envoyant également le message cr au Transcript. Ceci peut se faire par la
mise en séquence de deux transmissions comme ci-dessous:
Transcript show: ’Hello World’.
Transcript cr
|
|
où les deux transmissions sont séparées par un point, ou par une cascade
comme voici:
Transcript show: ’Hello World’; cr
|
|
Une cascade est la manière compacte de SMALLTALK d’envoyer plusieurs
messages au même receveur. La forme générale en est:
receveur message1 ; message2 ; ...; messagen
Le caractère “;” sépare les différents messages qui sont tous, l’un après l’autre
envoyés au même receveur. Ainsi dans notre exemple de Hello World
ci-dessus, Transcript reçoit d’abord le message show: avec son argument,
ensuite il reçoit le message cr. Le receveur traitant une telle cascade
exécute les messages l’un après l’autre et retourne la valeur de la dernière
transmission.
Maintenant que nous savons que la méthode answer: ne fait rien d’autre
qu’afficher un message dans la fenêtre Transcript, regardons la chaîne de
caractères qu’il affiche. Analysons donc l’expression:
(self class asString , ’ ’ , self nom , ’: ’ , aString)
Ce message est évidemment assez complexe, sinon il ne serait pas
entre parenthèses. Notons que les parenthèses servent ici, comme partout
ailleurs, à grouper ensemble les choses qu’on veut bien voir comme une
entité. En effet, l’expression parenthésée est suivie d’un point-virgule,
c’est donc une partie du premier message de cette cascade de messages
envoyé au Transcript. Clairement, c’est l’argument pour le sélecteur
show:, c’est la chaîne de caractères à afficher. Notons qu’en SMALLTALK
le délimitateur d’une chaîne de caractères est le caractère apostrophe
(’).
Cette chaîne de caractères est composée de 5 parties: le résultat de la
transmission self class asString, la chaîne se composant d’un caractère espace
(’ ’), le résultat de la transmission self nom, la chaîne de caractères ’: ’ et la
valeur de l’argument de answer:: «aString». Nous savons que ce sont ces cinq
parties qui composent la chaîne de caractères à afficher puisqu’elles sont séparées
par le caractère “,” (virgule). Ce caractère est le nom d’un sélecteur binaire qui
peut être envoyé à une chaîne de caractères et qui prend en argument
une autre chaîne de caractères. La chaîne receveur retourne alors une
nouvelle chaîne de caractères résultant de la concaténation des deux chaînes
receveur et argument. Si comme dans l’exemple ci-dessus nous avons quatre
occurences de ce sélecteur de concatenation (,), quelque chose de la forme
générale:
chaîne1 , chaîne2 , chaîne3 , ..., chainen
se comporte comme si nous avions écrit:
((...((chaîne1 , chaîne2) , chaîne3) , ...) , chainen)
donc: chaîne1 est le premier receveur du message “,” avec l’argument chaîne2,
ce qui livre une chaîne resultat1 qui, elle, recevra ensuite le message “,” avec
l’argument chaîne3, ce qui livre une chaîne résultat2 qui, elle, recevra le
message “,” avec l’argument ..., jusqu’à ce que le résultatn-2, qui est le
résultat de la concatenation de toutes les chaines de caractères de chaîne1
jusqu’à la chaînen-1, recevra le message “,” avec l’argument chaînen dont il
résultera finalement une chaîne de caractères représentant la concaténation
de toutes les chaînes données en argument à toutes ces transmissions.
Clairement: le résultat d’une transmission peut devenir le receveur d’une autre
transmission!
Gardons bien en tête cette possibilité que les receveurs de messages puissent -
eux-mêmes - être le résultat de transmissions précédentes, ce sont donc des
receveurs calculés, apparaissant de manière dynamique et éphémère lors de
l’exécution d’un programme.
Regardons alors les différentes parties de la chaîne à afficher dans
le Transcript. Le début de cette chaîne est la chaîne résultant de la
transmission
self class asString
Nous connaissons déjà le receveur self: ce sera le même objet que
celui qui reçoit le message answer:, une quelconque instance de la classe
animal ou de l’une de ses sous-classes. Rappelons que nous avons dit
dans le premier chapitre qu’une instance est composée des valeurs de ses
variables d’instance et d’un pointeur vers sa classe. Le message class
demande à une instance de bien retourner à l’émetteur la classe à laquelle
cette instance appartient. C’est donc là une classe qui reçoit le message
asString. Le message asString est implémenté dans la classe Object
et ne fait rien d’autre qu’envoyer le message printString au receveur
du message asString. printString, qui est défini dans beaucoup de
classes9,
demande au receveur de bien vouloir livrer une représentation de soi-même sous
forme de chaîne de caractères. Ici, la transmission self class asString se
réduit donc à la même chose que la transmission self class printString qui va
alors livrer le nom de la classe sous forme d’une chaîne de caractères.
La deuxième partie, ’ ’, est juste la chaîne de caractères composée d’un seul
caractère «espace».
La troisième partie, self nom, envoie encore un message à
self10,
donc au receveur du message answer:. Elle retourne la chaine de caractères
composée du nom de l’animal receveur. C’est un envoi du message nom que nous
avons défini plus haut.
La quatrième partie est la chaîne de caractères ’: ’ telle quelle, et la dernière
partie est la valeur du paramètre aString de la méthode answer:. La seule
transmission du message answer: que nous connaissons actuellement, est celle qui
se trouve dans la méthode parle où l’argument est effectivement une chaîne de
caractères, la chaîne ’je ne sais pas parler’.
Il est probablement temps de tester, un tant soit peu, le programme que nous
avons écrit jusqu’ici. Allons-y.