Bonjour à tous. Dans cette séquence, nous allons discuter
des messages et on va voir pourquoi avoir plein de
messages partout et des toutes petites méthodes, c'est
vraiment bien contrairement à ce que beaucoup de
développeurs peuvent penser. C'est une séquence
de conception qui est valable non seulement pour Pharo,
mais pour la conception dans tous les langages objet.
Comme nous l'avons vu, les envois de messages, ce sont
des hooks, des endroits où on va pouvoir passer du comportement.
Beaucoup de développeurs disent régulièrement "J'aime
bien les grosses méthodes parce que c'est facile à comprendre.
Il suffit de lire ligne à ligne et je comprends le code.
" Et en fait, dans cette séquence-là, je vais vous
montrer que ce n'est vraiment pas un bon point et qu'en
général, les grosses méthodes sont des problèmes de design.
Avoir une hiérarchie de classes et une même méthode
implémentée dans plusieurs classes, c'est une façon de faire du choix.
Si on prend une grosse classe avec plein d'opérations et
qu'il faut choisir l'opération en fonction de l'état, je vais mettre quoi?
Je vais avoir du code qui ressemble à "Si je suis dans
cet état-là, alors je fais cette opération.
Là, si je suis dans cet état-là, je fais cette
opération", et on va avoir tendance à avoir du gros code
avec des "if" partout.
Ce qui fait que quand on veut ajouter de nouveaux cas, il
faut modifier plein de méthodes partout.
Dans la version à droite, chaque cas
est séparé dans une classe dédiée.
Il suffit d'envoyer le message opération à un
objet pour que le if se fasse et le if n'a pas besoin d'être
écrit par le programmeur, ça se fait
automatiquement grâce au mécanisme de polymorphisme.
Dans les slides qui suivent, je vais vous montrer un
exemple et comment je peux améliorer un exemple en
changeant petit bout par petit bout.
Là, j'ai une grosse méthode un petit peu difficile à
comprendre qui fait pas mal de choses.
Ce qu'on aimerait, c'est que dans une sous-classe, cette
variable qui est là ait une valeur un petit peu différente.
Tel que le code se trouve, la seule solution pour faire
ça, d'abord, c'est de faire la sous-classe et ensuite de
recopier l'ensemble du code en faisant la petite
modification que l'on souhaite faire.
Dans les langages comme Java ou avec l'utilisation de
mots-clés private, si les attributs utilisés par la
méthode sont private, ce que je viens de faire, ce n'est pas possible.
Si une méthode utilise des variables d'instances private,
les sous-classes ne peuvent pas ré implémenter ou
dupliquer le code, donc ça peut ne pas être possible.
Mais de toute façon, dupliquer, ce n'est pas une bonne
pratique parce que la duplication copie les bugs.
Si j'avais un bug dans la version d'origine, je vais
avoir un bug dans chaque copie, ce n'est pas très bon.
Et dès que je vais modifier une copie, il va falloir que
je modifie toutes les duplications.
Ce n'est pas très bon, c'est du travail en plus pour les
développeurs et il faut se souvenir qu'il y a plusieurs copies, etc.
La solution, c'est les messages et l'envoi de messages.
L'idée, c'est que quand, dans une méthode, on va envoyer
un message plutôt que d'écrire le contenu de la méthode
correspondante directement à l'intérieur, les
sous-classes vont pouvoir modifier le comportement.
Si je prends la méthode bar, elle envoie foo à self, dans
A, foo retourne 10 et les sous-classes peuvent changer
cette valeur-là et mettre par exemple 42.
Comment est-ce qu'on peut améliorer le code qu'on vient
de voir de façon à pouvoir profiter du mécanisme d'héritage
sans faire de la duplication ?
Cette partie-là, je vais l'extraire dans une méthode et à
la place, je vais envoyer un message.
Il existe un refactoring qui s'appelle extract method qui
fait exactement ça. Vous avez des outils pour transformer
ce code-là en ce code-là. Le code que j'avais
sélectionné au slide d'avant a été transféré dans une
nouvelle méthode qui s'appelle ratio.
Et là où le code était écrit, on a maintenant un envoi de messages.
Ce qui signifie que dans les sous-classes, je peux
changer complètement ce comportement ou
rappeler le comportement de la super classe et faire une modification.
C'est ce que je fais ici.
J'envoie le message ratio à super pour exécuter le code
tel qu'il est dans la super classe et j'ajoute 10, c'est
ce que je voulais faire au départ.
Une autre étape possible, c'est d'extraire cette
partie-là de la même façon pour laisser les sous-classes
pouvoir changer ce comportement-là.
J'extrais ce bout de code dans une méthode
dédiée et, dans la méthode principale, j'envoie un message.
Ici, on voit que la classe à instancier est écrite en dur.
Ce qui fait que les sous-classes, si elles veulent
changer cette classe-là pour avoir potentiellement une
sous-classe de uiNode, il va falloir qu'elles recopient l'ensemble
de cette méthode. On peut faire exactement le même
processus et extraire la classe dans une méthode de façon
à ce que les sous-classes puissent changer la classe à instancier.
C'est ce que je fais ici.
J'extrais la partie qui m'intéresse dans une méthode.
Et ici, j'envoie un message.
Du fait que j'envoie un message, les sous-classes vont
pouvoir changer le comportement.
Il y a des développeurs, comme je vous l'ai dit au début,
qui ne seront pas d'accord parce qu'ils trouvent que c'est
difficile de lire plein de petites méthodes qui sont
réparties un petit peu partout dans le système et qui
préfèrent lire une grosse méthode ligne à ligne.
En fait, ça n'est pas une bonne pratique parce que lire
du code ligne à ligne, si l'application est très grosse,
on va bien se rendre compte que l'on ne peut pas lire
ligne à ligne, on ne peut pas comprendre ce qui se passe.
C'est là que les abstractions sont utiles et c'est là qu'extraire
des bouts de comportement des expressions dans des
méthodes va faire du sens parce qu'on va pouvoir lire
globalement une méthode sans en comprendre chacun des détails.
Les petites méthodes, c'est vraiment bien et il faut l'utiliser partout.
On peut continuer. Ici, on voit qu'on a la
valeur à 55 qui apparaît en dur dans le code de la
méthode, ce qui signifie que les sous-classes ne peuvent
pas changer et ne peuvent pas mettre 100 par exemple.
Donc, on peut extraire cette valeur-là comme on l'a fait
précédemment et mettre ça dans une méthode séparée de
façon à ce que les sous-classes changent cette valeur-là.
Un autre avantage de ça, c'est qu'avant, il y avait
marqué 55, maintenant c'est marqué self averageRatio.
On a remplacé une valeur numérique par un nom.
Et maintenant je sais à quoi correspond la valeur 55.
Je sais que c'est averageRatio.
En lisant le code, je comprends mieux.
Avoir plein de petites méthodes, ça aide aussi à la lecture du code.
Créer cette méthode averageRatio pour retourner 55 permet
à toutes les sous-classes de changer la valeur, mais
comment est-ce qu'on permet aux utilisateurs de la classe
de changer la valeur aussi ?
Ce qu'on peut faire, c'est passer par une variable d'instance.
La méthode averageRatio qui était utilisée dans le slide
précédent, maintenant, elle va retourner la valeur de la
variable d'instance s'il y a une valeur dedans, sinon ça
retourne la valeur par défaut.
La valeur par défaut, c'est 55.
Les utilisateurs d'un objet Node vont pouvoir mettre la
valeur qu'ils souhaitent à l'intérieur.
Avec cette conception, les sous-classes peuvent faire un
override de la méthode defaultAverageRatio pour changer la valeur.
Et les utilisateurs de la classe peuvent aussi changer la
valeur pendant l'exécution du programme.
C'est ce qu'on appelle le gruyère-oriented programming
dans le sens où le programme objet ou une méthode va
comporter des trous, on appelle ça des hooks, qui vont
pouvoir être remplis par les sous-classes.
En conclusion, le code peut être réutilisé et raffiné
dans les sous-classes.
À chaque fois qu'on va envoyer des messages, les
sous-classes vont pouvoir changer le comportement de la
super classe ou le raffiner ou le changer complètement.
Les petites méthodes, c'est vraiment bien parce que ça
donne des noms à des bouts d'expressions et parce que ça
laisse aux sous-classes le choix de changer le comportement.