Bonjour. Pour ce cours, on va parler essentiellement de
conception objet et on va regarder ce qui est l'essence
du dispatch ou de la liaison tardive dans le langage à
objet. Ce cours prend Pharo comme exemple et c'
est agréable de voir que Pharo est bien implémenté, mais
il y a une forte généralisation possible qu'on verra dans le cours qui suit.
Commençons.
Si on regarde les booléens, dans Pharo, c'est
tout ce qu'il y a de plus basique.
Vous avez des opérateurs booléens qui sont gloutons comme &, |, not.
Vous avez des opérateurs paresseux comme or: qui vont
évaluer leurs arguments que si nécessaire.
Vous avez aussi les conditions, ce qu'on verra dans un autre cours.
C'est bien implémenté, mais il n'y a rien de fantastique ou de spécifique.
La semaine dernière, je vous avais demandé de faire un exercice.
Je vous avais posé trois questions, je vous demandais
comment est-ce qu'on implémente not; comment est-ce qu'on
implémente |, et ça, c'est la question la plus
importante, pourquoi est-ce que je posais ces questions?
On va d'abord répondre aux 2 premières et puis dans la
séance qui suit, je répondrai à
la question trois.
L'exercice, c'était vraiment, j'ai false.
Si j'envoie le message not à false, ça va me rendre true.
Si j'envoie le message not à true, ça va me rendre false.
J'ai des objets, comment est-ce que j'implémente ça?
Je vais d'abord vous donner une indication: la solution
ne contient pas de conditions. En général, vous arrivez à
avoir une solution qui a une condition, mais moi je vous
assure que la solution que je propose ou que Pharo
implémente en fait ne contient absolument aucune
condition ou conditionnel.
Vous n'avez pas de if dans la solution.
Réfléchissez un peu pour voir si ça vous donne une idée.
En général, ça ne marche pas trop ce genre d'indications.
Donc je vais vous donner une deuxième indication: en
fait la solution a trois classes.
Elle a la classe Booléens qui est une classe abstraite.
Elle a la classe True et la classe False.
L'objet booléen true, c'est l'unique instance de la classe True.
Vous voyez la différence, l'
instance true commence par un petit t et la classe False
commence avec une majuscule.
False est l'unique instance de la classe False.
Qu'est-ce que ça donne sur un schéma?
On voit bien que true est instance de la classe True,
false est instance de la classe False.
Normalement avec cette indication-là, vous devriez me
dire "C'est évident, la solution doit être comme ça.
" Je ne sais pas si vous la voyez.
On va la voir dans un moment.
Je vous laisse un petit peu réfléchir.
Je ne vais pas parler tout de suite de la
solution, mais réfléchir de comment est-ce qu'on exprime
finalement un choix dans un langage à objets.
Un choix, on l'exprime en définissant des classes avec
des interfaces qui sont compatibles, avec des méthodes
compatibles et en envoyant un message à une instance.
Donc là, c'est ce que j'ai illustré.
Quand je fais x open,
je vais choisir la bonne méthode associée à x.
Ça veut dire que si c'est un fichier, ça va ouvrir un
fichier; si c'est une fenêtre, ça va ouvrir une fenêtre
; si c'est un outil, ça va ouvrir un outil.
La méthode va être sélectionnée suivant la base de la classe de x.
Comment avec cette indication on peut maintenant avoir la
solution? On va l'implémenter comme ça.
Ça veut dire quoi ?
Ça veut dire que j'implémente la méthode not dans la
classe False et dans ces cas-là, je rends true.
J'implémente la méthode not dans la classe True en rendant false.
De manière visuelle, on obtient ça.
Vous voyez que la solution n'a pas de conditions explicites.
Je n'utilise pas un if à l'intérieur de ce genre de choses.
Comment ça marche ?
Ça marche comme ça.
Ça veut dire que quand j'envoie le message not ici, où
est-ce que je recherche la méthode not?
Je la cherche dans la classe du receveur donc true est instance de True.
Ah ben tiens, c'est cette méthode-là qui va être exécutée.
Qu'est-ce que je dois faire ?
Je dois rendre false.
Ah ben tiens, ça marche.
Maintenant, j'envoie un message à l'instance false.
Je cherche où? Dans la classe de False.
C'est ce not-là qui va être exécuté donc ça rend true.
Donc, j'ai bien implémenté la négation de booléens avec
deux méthodes sans avoir besoin de conditions.
On peut regarder aussi l'implémentation de la super classe.
La classe Booléens est une classe abstraite.
Elle a deux sous-classes qui vont implémenter les opérateurs qu'il faut.
Et en Pharo, on va exprimer que not est une méthode
abstraite sur booléens en utilisant le message self subclassResponsability.
Ça, c'est juste l'aspect Pharo de la chose, mais c'était
pour être complet.
Maintenant, normalement vous devriez avoir compris
comment vous pouvez exprimer le comportement de |.
Je vais vous laisser un petit moment pour exprimer ça.
Là, l'idée c'est que vous allez devoir définir une
méthode qui va prendre un argument de plus et qui suivant
la valeur de cet argument, va rendre la bonne
chose. Là, souvent vous allez dire "Il suffit que j'aie une condition.
" Mais non, c'est ça le truc.
Là, encore une fois, vous n'avez pas besoin de conditions
pour implémenter |. Je vous laisse dix secondes pour réfléchir.
Normalement, vous auriez dû préparer l'exercice.
Là, je vais définir | sur la classe
Booléens abstraite comme une méthode abstraite.
Sur la classe False, je le vois c'est écrit, le
receveur est de la classe False, qu'est-ce que je dois rendre?
Ah ben tiens, quand c'est true, je rends true; quand c'est
false, je rends false et quand c'est n'importe quoi, je
rends n'importe quoi. Donc ça veut dire que je rends l'argument.
Là, l'implémentation sur la classe False de |, c'est
rendre l'argument qui est passé.
Et c'est exactement ce qu'on fait.
De la même manière, si on regarde sur la classe True, c'est
expliqué là.
Quand j'envoie | au receveur true,
je rends le receveur.
Donc là, je rends true.
Vous voyez encore une fois que je n'ai pas de conditions,
j'ai juste utilisé de l'envoi de message.
De manière un petit peu plus propre, comment c'est écrit dans Pharo?
On sait que true va être le receveur du message donc au
lieu d'écrire true ici, on peut écrire self, c'est pareil.
Si vous lisez la définition, vous pouvez voir self et
vous pouvez vous dire "Ah ben oui, c'est normal puisque
le receveur sera true donc ces deux écritures sont strictement équivalentes.
"
Là, encore une fois, si on regarde de manière visuelle,
quand j'envoie le message | avec quelque
chose ici à l'objet true, je vais chercher cette
définition-là de | et elle va me rendre self.
Donc, elle va me rendre true.
Celle-là, quand j'envoie le message | avec quelque chose,
je vais chercher dans la classe de False qui est False,
donc je tombe sur cette implémentation-là, donc je vais rendre alpha.
C'est bien les tables de booléens que je cherchais à
implémenter.
Ce qu'il faut voir, c'est que la solution qu'on a
implémentée n'utilise absolument pas de conditions ou
d’instructions conditionnelles comme une boucle explicite.
Qu'est-ce qu'elle fait ?
En fait, elle laisse le receveur décider.
Ça veut dire que je dis à l'objet booléen qui reçoit le
message "Trouve la bonne version, fais ce qu'il faut.
" Je ne suis pas en train de prendre une décision par
rapport à ce qui devrait être fait.
C'est un principe qu'on va retrouver un petit peu
ailleurs et qui est vraiment un principe fondamental objet.
C'est les heuristiques dont on parlait au début du cours,
c'est "Do not ask, tell.
" Ça veut dire "Je ne veux pas exprimer une condition, je
veux juste donner un ordre " Et ça, c'est une grande clé
de la programmation-objet.
Et l'autre, c'est "Laisse le receveur décider.
" Je donne un ordre au receveur, c'est à lui d'encapsuler
son savoir et de prendre les bonnes décisions.