Bonjour. Dans cette séquence on va voir la réflexion.
Donc on va aussi discuter de la capacité d'introspection
dans Pharo et pourquoi c'est intéressant.
Donc un système réflexif, la réflexion se décompose en 2
grandes familles, l'introspection.
L'introspection, c'est la capacité d'un programme à s'observer lui-même.
Et l'intercession, c'est la capacité d'un programme à se
modifier lui-même, a modifié donc son état, sa propre interprétation.
Donc la réification, c'est un concept qui consiste à
exprimer, à expliciter sous-forme d'objets, des choses
qui sont normalement implicites. Par exemple, la pile d'exécution,
la stack en Pharo est explicite.
En fait, on peut y avoir accès sous la forme d'un objet classique.
Donc plus classiquement, la notion de classe, une classe
en Pharo c'est un objet tout à fait classique.
Dans un autre langage, ce n'est vraiment pas le cas.
Les classes ne sont pas des objets.
Donc, un système réflexif comme je le disais, il a une
propre représentation de lui-même d'accord.
Il est capable de se représenter lui-même.
Et puis, il peut agir sur cette représentation pour se modifier lui-même.
Et lorsqu'il se modifie, ça change son état mais ça
change aussi sa représentation, d'accord.
Donc on parle de lien causal entre sa représentation et son état.
Si on change l'état, si l'état change, la représentation
du système change et si on change la représentation du
système, ça change l'état du système.
Donc pourquoi c'est intéressant tout ça?
En fait, c'est intéressant puisqu'on va se servir de ça,
de l'introspection et de l'intercession pour aller
regarder à l'intérieur des objets.
Donc ici, je définis une collection que je transforme en
collection ordonnée.
Et puis, je vais aller utiliser l'inspecteur de Pharo qui
est un outil et qui va être capable de regarder à l'intérieur de cet objet.
Donc cet objet, ça peut nous dire: c'est un objet
instance de la classe OrderedCollection, on le voit ici.
Cet objet, il a des variables d'instances: firstIndex, last index.
On a des valeurs, des variables d'instances de cet objet,
1, 12 et puis un tableau de 12 éléments.
D'accord. Donc on peut se demander comment est-ce que cet
outil l'inspecteur est capable d'aller regarder à l'intérieur des objets?
Comment est-ce qu'il peut voir l'état interne des objets?
En fait pour ça il utilise des méthodes d'introspection,
des méthodes qui vont aller inspecter l'intérieur de l'objet.
Donc, on en a plein qui sont définis sur la classe
object, donc instVarAt qui permet d'aller voir une
variable d'instance en fonction de son numéro, donc je
veux aller voir la variable d'instance numéro 1 de cet objet-là.
Je peux aller modifier la variable d'instance numéro 1 de
cet objet en fixant une nouvelle valeur, ou alors je peux
accéder à une variable d'instance par son nom ou la
modifier par son nom: instVarNamed put.
Donc, je vous donne des exemples.
Donc typiquement ici, on crée un point et puis on fait
donc le point c'est 10 3, instVarNamed X.
Je vais récupérer la valeur de la variable d'instance qui
s'appelle X de ce point donc ça me rend bien 10.
Ensuite, instVarNamed X put 33, donc j'ai changé
la valeur de la variable instance X de ce point, donc de
10 c'est passé à 33.
D'accord. Donc, j'ai été capable de modifier l'état
interne d'un objet en utilisant les capacités d'introspection
et d'intercession. Surtout l'intercession-là, instVarNamed put.
Le gros point important c'est qu'on a violé l'encapsulation,
c'est-à-dire que c'est un objet externe qui est venu
modifier cet objet-là, son état interne.
Donc ça viole complètement le principe d'encapsulation
mais c'est extrêmement utile pour construire des outils,
et pendant les phases de développement.
Donc c'est à proscrire dans le code normal d'une
application, mais par contre c'est extrêmement puissant
pour construire des outils génériques typiquement un inspecteur de codes.
Donc je vous donne un autre exemple de méthodes d'introspection,
la méthode classe qui permet de récupérer la classe d'un
objet qui est définie sur la classe objet.
Donc, je peux demander à cette chaîne de caractères quelle est sa classe?
Cela va me rendre à la classe Stream, ce point quelle est sa classe?
La classe point, small top classe, je peux demander à la
classe classe quelle est sa classe.
? Et puis, rajouter classe, classe.
En gros, je peux introspecter le système en lui envoyant
le message classe aux objets pour savoir de qui ils sont
instances, de quelle classe ?
On en a plein des méthodes pour introspecter le système.
Donc ici, je prends la classe OrderedCollection puis je
vais lui envoyer plein de messages d'introspection.
Donc, je vais récupérer l'ensemble de ces super classes,
tous ces sélecteurs, le nom de l'ensemble de ces
variables d'instances, l'ensemble des sélecteurs.
Je veux récupérer l'ensemble des noms de ses variables d'instances,
l'ensemble de ces sous-classes.
Et caetera. L'ensemble de ces lignes de codes.
Donc en fait tout ça, nous permet de construire des
outils de plus haut niveau, tel que le système navigation ici d'accord.
Donc ce système navigation va permettre de montrer ou d'interroger
le système au plus haut niveau.
Donc par exemple si je veux voir l'ensemble des classes
qui implémentent la méthode virgule.
Ici, on voit qu'on a une fenêtre qui va s'ouvrir et puis
on va voir tous les implémenters de virgules, donc la
classe abstract fire reference implémente bien la méthode virgule, d'accord.
Et j'ai la liste de toutes les classes qui implémentent cette méthode-là.
Alors un autre exemple.
On voudrait implémenter un menu ou
un bouton, et pour ça quand on clique sur le bouton on
voudrait envoyer un message à l'objet qu'il y a derrière,
en fonction du nom du bouton par exemple.
Comment est-ce que je peux faire pour transformer en fait
une chaîne de caractères en message à envoyer à un objet?
Donc voilà, j'ai une méthode d'intercession qui permet de
faire ça, perform qui est défini sur object, donc je lui
passe un symbole donc le nom d'un message à exécuter.
Et puis ça va envoyer ce message-là à l'objet en question.
Donc j'ai un deuxième typede message, donc c'est perform
with, où je peux donner le nom du symbole, le nom de la
méthode à exécuter, et puis passer une liste d'arguments.
Donc je vous donne un exemple si je fais 5 factoriels,
donc ça c'est l'envoi de message classique, et l'envoi de
message réflexif, c'est 5 perform dièse factoriel.
Donc là, c'est un symbole.
Donc ça veut dire objet 5 s'il te plaît exécute ou reçoit
le message factoriel.
Et donc, il y a le look-up normal qui est appliqué et ces
2 formes sont complètement équivalentes.
Donc, je vais maintenant vous décrire un autre exemple.
En fait ici on a un inspecteur de codes: OrderedCollection.
On voit l'intérieur de la classe OrderedCollection.
Donc cette classe elle contient des attributs, enfin
des variables d'instances.
Par exemple, methodDict, en fait, on voit que la classe
OrderedCollection elle hérite bien de Behavior et donc
elle a bien une methodDict ici, qui est une instance de
dictionnaire de méthodes qui lui contient CompiledMethod.
Et donc on voit bien ce dictionnaire des méthodes
contient une CompiledMethod ici; et caetera, et caetera,
une autre CompiledMethod et caetera.
Donc qui contient plein de CompiledMethod.
Et on va être capable pour chacune de ces CompiledMethod,
donc ces méthodes compilées de leur demander leurs codes source.
Si je fais self getSource, je récupère le code source de
la méthode compilée.
Mais on peut aller encore plus loin: une CompiledMethod
on peut lui demander de s'exécuter directement, avec le
message valueWithReceiver arguments, d'accord.
Donc attention, si je fais ça il n'y a pas de lookup.
Vu que je tiens déjà une méthode compilée, elle s'exécute
directement, il n'y a pas d'envoi de message.
Donc ici, je vais demander à la classe integer de
récupérer sa CompiledMethod qui s'appelle factoriel.
Je récupère bien une instance de la classe CompiledMethod.
Je lui envoie le message valueWithReceiver arguments.
Donc, je lui passe des arguments.
Donc le receveur ça sera 5.
Les arguments, ça sera tableau vide puisque c'est un
message unaire, et on va bien récupérer le résultat.
Donc, ça revient à déclencher l'exécution de la CompiledMethod sans look up.
Donc en résumé, la réflexion c'est extrêmement puissant on l'a vu.
C'est quelque chose qui permet d'introspecter tout le
système, puisqu'il est représenté en lui-même sous la forme d'objets.
Mais aussi d'aller modifier l'état du système.
Cela permet de construire de façon générique des outils sur tous les objets.
Donc, vous pouvez vraiment dialoguer avec les objets avec
un protocole qui est le même pour tous les objets du
point de vue structurel. Par contre attention, ça va
violer les principes d'encapsulation, ce n'est pas à
utiliser dans du code métier, c'est à utiliser pour la
construction d'outils et ça facilite notamment l'écriture
d'inspecteurs de code, comme on a pu le voir.
Vous pouvez d'ailleurs aller voir comment il est implémenté en Pharo.