Retrouvez cet article dans : Linux Magazine 89
Maintenant que nous avons présenté plusieurs aspects de Smalltalk, il est temps de revenir en détail sur le modèle objet de Smalltalk. Ce modèle est simple et ses principes de base sont appliqués de manière uniforme. Cette application uniforme bien que très naturelle est souvent source de confusion chez le novice. Comme tout est objet, en Smalltalk, les classes sont des objets comme les autres. Cet aspect est parfois déroutant.Un modèle uniforme et simple
Smalltalk est un langage objet pur. Dans un article précédent, nous l’avions décrit à l’aide des règles suivantes :- Règle 1 – Tout est un objet.
- Règle 2 – Un objet est instance d’une classe.
- Règle 3 – Une classe définit la structure (variables d’instance) des instances et spécifie les méthodes qui seront exécutées en réponse aux messages envoyés aux instances de la classe. Les variables d’instance sont privées à l’objet lui-même et les méthodes sont publiques.
- Règle 4 – Une classe peut hériter d’une autre classe par héritage simple.
- Règle 5 – Les objets communiquent entre eux uniquement par envoi de messages. Lorsqu’un objet reçoit un message, il devient actif : la méthode correspondante est recherchée dans la classe du receveur, si elle n’existe pas, la recherche se poursuit dans la super-classe.
- Règle 6 – Toutes les classes héritent indirectement de la classe Object.
- Règle 7 – Les classes sont les uniques instances de classes appelées méta-classes.

Figure 1 – Recherche d’une méthode dans les super-classes
brightness à une instance de TranslucentColor, la méthode associée est cherchée dans la classe du receveur donc TranslucentColor, puis la classe Color où elle se trouve effectivement. Il est intéressant de s’apercevoir que deux liens différents sont suivis : tout d’abord celui d’instanciation, puis celui d’héritage. A priori, rien de bien nouveau ! Mais vous allez voir que les implications bien que naturelles sont parfois déroutantes. Regardons maintenant les conséquences de la règle 7.
Classes et métaclasses
Puisque tout est objet, une classe est aussi un objet. Donc, elle est instance d’une autre classe nommée en Smalltalk sa métaclasse. En Smalltalk une classe est l’unique instance de sa métaclasse. Les métaclasses sont anonymes en Smalltalk : elles portent le nom de leur unique instance, suivi de class. La classe Point est l’unique instance de la classe Point class. La figure suivante illustre ce point : aTColor est une instance de la classe TranslucentColor. Cette dernière est un objet donc instance de la classe TranslucentColor class.

Figure 2 – Relations entre classes et méta-classes
Smalltalk impose une contrainte : la hiérarchie d’héritage des classes et des métaclasses est parallèle comme le montre la figure suivante : Translucent class hérite de Color class qui hérite d’Object class.

Figure 3 – Les méta-classes héritent les unes des autres
En Smalltalk, il n’existe qu’un seul mécanisme pour invoquer des méthodes et c’est celui que nous avons montré dans la première figure de cet article. Ce mécanisme fonctionne aussi pour les messages envoyés aux classes (car les classes sont des objets comme les autres donc la machine virtuelle ne fait pas de distinction). Lorsqu’un message est envoyé à une classe, ce message est recherché dans la classe du receveur, donc dans la métaclasse. Par exemple, lorsqu’on envoie le message r:g:b: à la classe TranslucentColor pour créer une couleur, cette méthode est recherchée dans la classe Translucent class, puis comme elle n’est pas définie dans cette classe, elle est recherchée dans la classe Color class. La méthode à exécuter est toujours recherchée dans la classe du receveur du message, quelle que soit la nature de celui-ci. Il n’y a aucune exception à cette règle.

Figure 4 – Recherche d’une méthode de classe
En fait, quand vous modifiez une classe en Smalltalk, vous éditez le code de deux classes distinctes ! Le navigateur de code (System Browser) groupe une classe et sa métaclasse. Le bouton " instance/class " permet de passer d’une classe à l’autre. Le bouton " instance " permet d’éditer les méthodes qui s’appliquent sur les instances de classe. Par exemple, hue est une méthode d’instance qui est exécutée sur une instance de la classe Color. La méthode de classe wheel: est une méthode qui est exécutée sur la classe Color elle-même (figure 5).Variables de classes et autres
Il faut ajouter au modèle objet de Smalltalk : les blocs, les variables de Pool, les variables d’instance de classes, les méthodes de classes et les variables de classes. Dans un précédent article, nous avons déjà abordé les blocs qui sont des méthodes anonymes. Nous présentons ici les autres éléments.Variables de Pool
Les variables de Pool sont des variables qui peuvent être partagées entre plusieurs classes n’étant pas forcément en relation d’héritage. Il est à noter que les variables de Pool et les dictionnaires de Pool qui les contiennent sont un des aspects les moins intéressants de Smalltalk. Néanmoins, nous les présentons ici afin que le lecteur puisse comprendre de quoi il s’agit lorsqu’il parcourt du code. Alors que les dictionnaires de Pool étaient traditionnellement définis en créant un dictionnaire dans une variable globale, Squeak utilise maintenant des sous-classes de SharedPool. Avec la nouvelle manière de définir des PoolDictionary, des variables de classes sont utilisées à la place de dictionnaires. Mais encore une fois, nous ne voulons pas entrer trop dans les détails.
Figure 5 – Vision des deux classes dans le navigateur de classes
Une classe voulant accéder aux variables de Pool doit mentionner, dans sa définition, le dictionnaire de variables de pool qu’elle souhaite utiliser. Par exemple, la classe Text indique qu’elle a besoin d’accéder au dictionnaire de Pool nommé TextConstants qui regroupe tous les caractères pouvant être utilisés dans des textes. Par exemple, le dictionnaire TextConstants possède la clef CR qui a pour valeur le caractère cr.ArrayedCollection subclass: #Text instanceVariableNames: ‘string runs’ classVariableNames: ‘’ poolDictionaries: ‘TextConstants’ category: ‘Collections-Text’Cela permet aux méthodes de cette classe d’accéder aux constantes directement en référençant les clefs du dictionnaire. Ainsi, on peut écrire la méthode suivante qui rend toujours la valeur vrai (true). On voit que l’on peut utiliser directement CR dans le code de la méthode.
Text>>testCR
^ CR == Character cr
Il est très rare d’avoir besoin de variables de Pool. Nous vous suggérons de ne pas les utiliser !
Variables d’instances de classes
Les variables d’instance de classes et les méthodes de classes sont simplement des variables d’instances et des méthodes, mais définies sur les classes. Elles sont donc définies en éditant la métaclasse d’une classe, puisqu’elles définissent des attributs et comportements qui seront appliqués sur une classe et non un objet : par exemple, si on souhaite implémenter le schéma de conception (design pattern) Singleton qui ne permet à une classe de n’avoir qu’une seule instance d’une classe. Imaginons que nous voulions implémenter le Singleton sur la classe WebServer. Nous définissons une variable d’instance de classe nommée uniqueInstance et des méthodes de classes comme suit, puis nous ajoutons une variable d’instance à la classe WebServer class. La méthode de classe uniqueInstance vérifie si l’instance a déjà été créée. Si ce n’est pas le cas, une instance est créée et associée à la variable d’instance et, enfin, nous retournons la valeur de cette variable, car dans tous les cas, elle pointe sur la seule instance de la classe.Object subclass: #WebServer
instanceVariableNames: ‘ ‘
classVariableNames: ‘’
poolDictionaries: ‘’
category: ‘Graphics-Primitives’
WebServer class
instanceVariableNames: ‘uniqueInstance’
WebServer class>>uniqueInstance
uniqueInstance isNil
ifTrue: [ uniqueInstance := self new].
^ uniqueInstance
WebServer class>>reset
uniqueInstance := nil
Il faut noter que, comme les classes et les métaclasses suivent exactement les mêmes règles, il n’y a aucune différence particulière entre variables d’instances et variables d’instances de classes. Attention, les variables d’instances sont privées : cela veut dire qu’il n’est pas possible d’accéder aux variables d’instances d’une instance nouvellement créée dans une méthode de classe. De la même manière qu’il n’est pas possible de le faire pour un autre objet. Pour accéder à l’état d’une instance, il faut nécessairement utiliser une méthode.
Variables de classes ou partagées
Le modèle objet de Smalltalk contient une autre sorte de variable nommée en anglais " class variable " (variable de classe en français). Pour être plus clair, ces variables auraient dû être nommées variables partagées. Une variable de classe est une variable qui est accessible par toutes les méthodes d’instance et de classe d’une classe et de ses sous-classes. Elle commence par une majuscule pour bien montrer que sa visibilité est plus importante qu’une variable d’instance. Elle permet de définir des valeurs qui ont une durée de vie plus grande que celle des instances. La classe Color définit une certain nombre de variables de classes (peut-être même trop).Object subclass: #Color instanceVariableNames: ‘rgb cachedDepth cachedBitPattern’ classVariableNames: ‘Black Blue BlueShift Brown CachedColormaps ColorChart ColorNames ComponentMask ComponentMax Cyan DarkGray Gray GrayToIndexMap Green GreenShift HalfComponentMask HighLightBitmaps IndexedColors LightBlue LightBrown LightCyan LightGray LightGreen LightMagenta LightOrange LightRed LightYellow Magenta MaskingMap Orange PaleBlue PaleBuff PaleGreen PaleMagenta PaleOrange PalePeach PaleRed PaleTan PaleYellow PureBlue PureCyan PureGreen PureMagenta PureRed PureYellow RandomStream Red RedShift TranslucentPatterns Transparent VeryDarkGray VeryLightGray VeryPaleRed VeryVeryDarkGray VeryVeryLightGray White Yellow’ poolDictionaries: ‘’ category: ‘Graphics-Primitives’Par exemple, la variable ColorNames est un tableau contenant le nom des couleurs définies par Squeak. Ce tableau est partagé par toutes les instances de Color et TranslucentColor et il est accessible dans les méthodes de classes et d’instance. Par exemple, la méthode name utilise la variable partagée pour retrouver le nom de certaines couleurs. L’idée ici est que comme seul un petit nombre de couleurs peuvent être nommées, il n’est pas nécessaire d’ajouter un champ name à chaque couleur, mais de chercher dans la table si nécessaire.
Color>>name
"Return this color’s name, or nil if it has no name. Only returns a name if it exactly matches the named color."
ColorNames do: [:name | (Color perform: name) = self ifTrue: [^ name]].
^ nil
Color class>>initializeNames
"Name some colors."
"Color initializeNames"
ColorNames := OrderedCollection new.
self named: #black put: (Color r: 0 g: 0 b: 0).
self named: #veryVeryDarkGray put: (Color r: 0.125 g: 0.125 b: 0.125).
self named: #veryDarkGray put: (Color r: 0.25 g: 0.25 b: 0.25).
...
Notez que la présence de variables de classes ou variables d’instances de classe pose la question de savoir quand les initialiser. Souvent une initialisation paresseuse est suffisante. Paresseuse signifie que la variable n’est initialisée que la première fois qu’elle est utilisée. Mais cela implique d’utiliser une méthode accesseur et de tester si la variable a été initialisée comme pour la méthode uniqueInstance. Une autre façon de procéder est basée sur l’utilisation de la méthode de classe initialize. Cette méthode quand elle est définie sur une classe est automatiquement invoquée quand la classe est chargée en mémoire. En la redéfinissant, on garantit ainsi que les variables de classe possèdent bien les valeurs souhaitées.
Démystifions le noyau de Smalltalk
Nous sommes sûrs que certains d’entre vous se sont demandés quelle est la classe d’une métaclasse et que se passe-t-il quand un message de classe est envoyé et qu’il n’est pas défini dans la hiérarchie de classe TranslucentColor class, Color class et Object class ? On peut développer en Smalltalk sans avoir la réponse à ces questions, mais il s’agit de questions fort intéressantes et les réponses n’ont rien de magiques et suivent les règles que nous avons énoncées précédemment. On voit là toute la puissance conceptuelle du modèle de Smalltalk, car il est écrit en lui-même. Il reste donc possible de le comprendre (et éventuellement de le modifier). Aux règles précédentes, nous ajoutons les règles suivantes :- Règle 8 – La hiérarchie d’héritage des métaclasses est parallèle à celle des classes.
- Règle 9 – Une métaclasse hérite indirectement de la classe Behavior via la classe Class.
- Règle 10 – Une métaclasse est instance de la classe Metaclass. En particulier, la métaclasse de la classe Metaclass est instance de Metaclass.

Figure 6 – Où les classes Behavior, ClassDescription et Class eantrent en jeu
TranslucentColor class est un objet, donc instance d’une classe. Il nous reste à savoir de quelle classe. Translucent class est une classe spéciale, i. e une métaclasse : elle est anonyme et ne possède qu’une instance donc elle est instance de la classe Metaclass. C’est ce que la règle 10 énonce. Ainsi, toutes les métaclasses Color class, Object class, mais aussi Class class, Behavior class sont des instances de la classe Metaclass. La classe Metaclass est l’unique instance de la classe Metaclass class (règle 7). Metaclass class est instance de la classe Metaclass (règle 10). La figure suivante montre l’ensemble du noyau Smalltalk. Non, ne prenez pas peur !

Figure 7 – Où tout se complique !
Ici, on voit l’astuce de la création du noyau que l’on appelle souvent le " bootstrap " (amorce en français). Le bootstrap permet à un système de se définir lui-même. Cela peut paraître complexe, mais il n’en est rien : il suffit de suivre les règles que nous avons énoncées ! Les classes Behavior et ClassDescription représentent le comportement commun entre les classes et les métaclasses. ClassDescription introduit les éléments comme les catégories qui permettent au développeur de structurer ses classes, alors que Behavior définit le minimum nécessaire pour la machine virtuelle.
Mais à quoi sert tout cela ?
Vous avez pu vous poser cette question au cours de la lecture de cet article. Au-delà de l’explication qui permet de mieux comprendre le modèle objet de Smalltalk et sa simplicité, y-a-t-il de véritables applications aux notions vues ci-dessus ? Oui, par exemple, utilisons ce modèle pour calculer des métriques (c’est-à -dire des mesures) sur le code de Squeak. Chaque objet Smalltalk peut répondre au message class qui lui permet de savoir à quelle classe il appartient : 1 class. retourne SmallInteger, 1 class class. retourne SmallInteger class et 1 class class class. retourne Metaclass. Enfin, on peut vérifier que : 1 class class class class class == 1 class class class. retourne true. Nous vous laissons chercher pourquoi. Essayons de répondre maintenant à quelques questions :
- Combien de classes contient une image Squeak ?
Object allSubclasses size. retourne 4751. Si on ajoute la classe Object, cela nous fait 4752 classes dans l’image que nous sommes en train d’utiliser (ce nombre peut varier en fonction de votre image). Notez qu’ici nous comptons aussi les métaclasses.
- Combien y-a-t-il de métaclasses dans une image Squeak ?
Metaclass allInstances size. retourne 2380 métaclasses (instances de Metaclass).
- Quelle est la classe qui contient le plus de méthodes ?
Object allSubclasses detectMax: [:each | each methodDict size]. retourne Morph. On fait une itération sur toutes les sous-classes de Object et, pour chaque classe, on détermine le nombre de méthodes (les méthodes sont conservées dans un dictionnaire dont on calcule la taille). On utilise une itération qui détermine l’élément (la classe) qui a la valeur maximale. Juste par curiosité, calculons : Morph methodDict size. retourne 1165 ! Sans même allez voir le code, on peut conclure qu’une classe qui possède autant de méthodes doit être mal conçue.
En conclusion
Nous avons montré que le modèle de Smalltalk est décrit par un ensemble limité de règles simples. Ainsi, les méthodes et variables d’instances de classes sont des méthodes et variables d’instances définies sur les métaclasses qui ne sont que les classes des classes. Contrairement aux méthodes statiques de Java, les méthodes de classes sont des méthodes normales et suivent les mêmes règles que les méthodes d’instances. On peut utiliser super comme dans n’importe quelle méthode. En fait, il n’y a qu’un seul modèle : un objet est instance d’une classe et les méthodes envoyées à cet objet sont recherchées dans la classe de l’objet, et ceci, quel que soit l’objet receveur (même s’il s’agit d’une classe). Nous avons aussi montré comment le noyau Smalltalk suivait des règles très simples. Il est lui-même le résultat d’une modélisation objet ! La classe Metaclass spécifie le comportement des métaclasses (instance unique, anonyme). La classe Class définit le comportement de toutes les classes. Metaclass et Class héritent leurs comportements de ClassDescription et Behavior. ClassDescription hérite de la classe Behavior qui représente l’information minimale requise par le système pour créer des objets et leur envoyer des messages ainsi que les comportements pour que le programmeur organise les méthodes. Tout ceci peut paraître complexe, mais montre la transparence de Smalltalk par rapport aux concepts mis en avant et leur utilisation par le système lui-même. Le modèle objet de Smalltalk est principalement utilisé par les outils de développement comme le navigateur de classes pour afficher des informations sur le système et permettre une navigation facile dans les classes et méta-classes.
Liens :
- Le site officiel : http://www.squeak.org/
- Le wiki de la communauté française : http://community.ofset.org/wiki/Squeak
- Des livres gratuits en ligne sur Smalltalk et Squeak : http://www.listic.univ-savoie.fr/~ducasse/FreeBooks.html
- Un livre sur Squeak en français : http://www.iam.unibe.ch/~ducasse/Books.html : BRIFFAULT (X.), DUCASSE (S.), Squeak, Eyrolles, 2002.
Retrouvez cet article dans : Linux Magazine 89





Donnez votre avis
Vous devez avoir ouvert une session pour écrire un commentaire.