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”).

PIC

FIG. 2.6: L’explorateur de classes après création de la méthode nom


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.