2.4 Ajout de méthodes aux sous-classes de la classe Animal
Jusqu’à maintenant, toutes nos méthodes se trouvent dans la classe Animal. Tous
les animaux ont donc le même comportement. Si l’on demande à une
instance de la classe Perroquet ou Baleine de parler, elles vont toutes
uniformément répondre je ne sais pas parler. Ainsi l’activation de la
transmission:
Perroquet new nom: ’Polly’; parle
résulte dans l’affichage, dans le Transcript, de
Perroquet Polly: je ne sais pas parler
et la transmission:
Baleine new nom: ’Moby’; parle
résulte dans l’affichage de
Baleine Moby: je ne sais pas parler
C’est plutôt ennuyeux comme comportement.
Commençons par rendre le comportement des Perroquets un peu plus
spécifique, puisque, nous le savons tous, certains perroquets savent apprendre à
parler. Pour cela, retournons à notre explorateur de classes, sélectionnons
la classe Perroquet et commençons à créer les protocoles initialize
et actions, de la même manière que nous l’avons faite pour la classe
Animal (cf. page 75). Comme nous avons donné la variable d’instance
vocabulaire à notre classe Perroquet, une variable qui devrait contenir à tout
instant le vocabulaire de chacun de nos perroquets particuliers, nous avons
donc besoin d’une méthode d’initialisation de cette variable. Appelons-la
vocabulaire: et mettons la dans le protocole initialize. Nous avons
également besoin d’une méthode pour dire aux perroquets comment parler.
Cette deuxième méthode s’appelera tout naturellement parle et nous le
mettrons dans le protocole actions de la classe Perroquet. Voici ces deux
méthodes (n’oubliez pas de faire accepter, avec les touches «Alt+s», chacune
d’elles):
vocabulaire: aString
"apprend au perroquet un nouveau vocabulaire"
vocabulaire := aString
parle
"fait parler le perroquet avec son vocabulaire"
self answer: vocabulaire
|
|
Testons alors le comportement des perroquets. Pour cela, créons un perroquet,
donnons lui un nom et un vocabulaire et demandons lui ensuite de nous dire
quelque chose. Par exemple, si nous entrons dans le Workspace l’expression
suivante:
Perroquet new nom: ’Polly’;
vocabulaire: ’Screatch@#!? Go away’;
parle
|
|
et nous faisons un doIt (touches «Alt+d») sur cette expression, nous verrons
apparaître dans le Transcript la ligne:
Perroquet Polly: Screatch#!? Go away
Nous avons pu créer au moins un animal qui sait dire autre chose que “je ne
sais pas parler”!
Comment SMALLTALK a-t-il procédé? D’abord nous avons, avec l’expression
Perroquet new, créé une instance de la classe Perroquet. À cette instance nous
avons ensuite envoyé le message nom: avec comme argument la chaine ’Polly’, le
nom de tout perroquet dans les pays anglophones. L’instance de la classe
Perroquet que nous venions de créer a cherché dans sa classe une méthode
correspondant au sélecteur nom:. N’en trouvant aucune de ce nom, elle a cherché
dans sa sur-classe, Oiseau. Cette classe ne contenant aucune méthode de ce nom
non plus - elle ne contient aucune méthode - notre instance a continué alors à
chercher une telle méthode dans la sur-classe de sa sur-classe, donc dans
la classe Animal. Enfin, là, elle a trouvé la méthode de ce nom (de ce
sélecteur):
nom: aString
"change le nom de l’animal en aString"
nom := aString
|
|
disant d’affecter à sa variable d’instance une nouvelle valeur, la valeur de
l’argument que la transmission reçue contenait, la chaîne de caractères ’Polly’.
À partir de cet instant nous pouvons parler de cette instance particulière de
la classe Perroquet comme du perroquet Polly. Nous dirons que les
instances de la classe Perroquet héritent de la méthode nom: de la classe
Animal.
Notons bien que pour trouver la méthode correspondante au sélecteur d’un
message, l’instance commence à chercher ce sélecteur dans sa classe, et si elle ne le
trouve pas, remonte dans la chaîne de ses sur-classes, jusqu’à ce qu’elle trouve une
méthode correspondante. C’est cette méthode qui sera alors exécutée. Si
elle ne trouve nulle part une telle méthode - la recherche serait alors
remontée jusqu’à la classe Object - SMALLTALK enverrait alors le message
doesNotUnderstand: à l’instance, avec comme argument le sélecteur dont on ne
trouve nulle part une implémentation, ce qui, normalement, provoque une
erreur. Notons aussi que l’occurrence d’un nom de variable d’instance
dans une méthode désigne toujours la variable d’instance du receveur
du message - indépendamment de la classe dans laquelle se trouve la
méthode.
Continuons l’analyse de notre transmission
Perroquet new nom: ’Polly’;
vocabulaire: ’Screatch@#!? Go away’;
parle
|
|
Après la transmission Perroquet new nom: ’Polly’, elle continue, en
cascade – indiqué par le caractère point-virgule – par envoyer à notre
perroquet nommé Polly le message vocabulaire: avec la chaîne de caractères
’Screatch#!? Go away’. Polly trouvant alors cette méthode dans sa propre
classe peut l’éxecuter immédiatement et donne donc une nouvelle valeur à sa
variable d’instance vocabulaire. Maintenant Polly se distingue de toutes les
autres instances de la classe Perroquet par son nom et par son vocabulaire -
bref, comme nous l’avons dit dans l’introduction, par les valeurs de ses variables
d’instance.
La figure 2.9 page 101 montre un Workspace avec un exemple de trois
transmissions successives à un perroquet nommé Polly et les résultats de ces
transmissions dans le Transcript. Dans cet exemple nous avons gardé une
nouvelle instance de la classe Perroquet dans la variable globale Polly, afin de
pouvoir lui envoyer plusieurs messages, l’un à la suite de l’autre. Pour
chacune des lignes du Workspace, nous avons lancé un doIt avec les touches
«Alt+d».19
Comme on peut le voir dans le Transcript, notre perroquet manque un
peu d’intelligence: chaque fois qu’il apprend un nouveau vocabulaire, il
oublie ce qu’il savait dire avant cet apprentissage. Imaginons alors que les
perroquets puissent acquérir un vocabulaire de plus d’une phrase, et qu’ils en
répondent, quand on leur demande de parler, avec une phrase aléatoire de ce
vocabulaire.