4.4.7 les variables de classe

Les variables de classe sont des variables globales à une classe et ses sous-classes. Elles sont déclarées lors de la définition d’une classe avec le paramètre classVariableNames:. Voici, pour exemple, la définition de la classe Character du système SQUEAK:
Magnitude subclass: #Character  
    instanceVariableNames: ’’  
    classVariableNames: ’CharacterTable  
                         ClassificationTable  
                         LetterBits  
                         LowercaseBit  
                         UppercaseBit’  
    poolDictionaries: ’’  
    category: ’Collections-Text
Cette classe définit cinq variables de classes, les arguments du paramètre classVariableNames:. Ces variables sont accessibles à la classe même, ainsi qu’à toute instance de cette classe et de ses sous-classes éventuelles, donc à tout caractère. À l’extérieur de la classe et de ses sous-classes les variables de classe sont inconnues et leur consultation produit une erreur variable indéfinie. La portée d’une telle variable est donc considérablement plus restreinte que celle d’une variable globale: seulement la classe et des instance de la classe (et les sous-classes et instances des sous-classes de la classe), dans laquelle une variable de classe est définie, peuvent consulter et modifier ses variables de classe.

Pour exemple, reprenons notre classe Test définie plus haut et supposons qu’aucune instance de la classe devrait avoir plus que 10 éléments. Sachant que les méthodes de création avec les variantes du message with: se limitent à créer des instances avec au maximum six éléments, nous n’avons pas besoin de toucher à cet ensemble de méthodes. La seule méthode de création susceptible de produire des instances de taille arbitraire est la méthode new:. Nous pouvons la redéfinir, comme méthode de classe, dans la classe Test comme:

new: n  
   n > 10  
     ifTrue: [self error: ’trop d’éléments’]  
     ifFalse: [super new: n]
et, à partir de la compilation de cette méthode, si l’on veut créer une instance de la classe Test de plus que dix éléments, bien entendu, la transmission du message error:51 se déclenche et aucune instance ne sera créée.

Si nous décidons par la suite que nous ne pouvons admettre que des instances ayant moins que 16 éléments, nous pouvons modifier cette méthode, en remplaçant l’entier 10 par l’entier 15 et recompiler la méthode. Ce n’est pas très élégant, surtout, si la décision est le résultat d’un calcul et la modification devrait se faire automatiquement. Dans ce cas, il serait probablement plus simple d’ajouter une variable de classe, nommée par exemple NombreMaxDElements, à la définition de notre classe Test,

ArrayedCollection variableSubclass: #Test  
    instanceVariableNames: ’’  
    classVariableNames: ’NombreMaxDElements’  
    poolDictionaries: ’’  
    category: ’Cours-Smalltalk’
et de l’initialiser à la valeur voulue avec une transmission utilisant la méthode ci-dessous:52
nombreMaxDElements: n  
        NombreMaxDElements := n
Ensuite il faut remplacer la méthode new: ci-dessus par:
new: n  
   n > NombreMaxDElements  
     ifTrue: [self error: ’trop d’éléments’]  
     ifFalse: [super new: n]
Ainsi, chaque fois que vous décidez d’un autre nombre d’éléments maximal, il suffit de changer la valeur de la variable de classe NombreMaxDEelements en ce nouveau nombre et il n’est plus nécessaire de recompiler la méthode new:.

L’exemple ci-dessus était une utilisation possible d’une variable de classe. Nous aurions, comme nous l’avons indiqué, pu faire la même chose sans utiliser ce type de variable, sans utiliser une variable partagée: de manière moins élégante, certes, mais de manière aussi efficace.

Supposons alors que nous voulions qu’il n’existe jamais plus d’instances que le nombre maximal d’éléments qu’elles peuvent contenir. Comment faire sans utiliser une variable partagée? Pour résoudre ce problème, il faudrait qu’à chaque création d’une instance nous gardions un souvenir de ce fait et qu’après NombreMaxDElements créations nous interdisions toute création ultérieure.53

Commençons alors par créer une variable de classe supplémentaire nommée NombreDInstances:

ArrayedCollection variableSubclass: #Test  
    instanceVariableNames: ’’  
    classVariableNames: ’NombreMaxDElements  
                         NombreDInstances’  
    poolDictionaries: ’’  
    category: ’Cours-Smalltalk’
qui devra contenir à tout instant le nombre d’instances créées avec le message new: propre à la classe Test. Bien entendu, nous devons aussi modifier le message new: pour qu’il réponde à ces nouvelles contraintes: il faut qu’il vérifie à la fois que la nouvelle instance ne contienne pas trop d’éléments et que le nombre total d’instances ne soit pas supérieur au nombre d’éléments permis. Dans la foulée, implémentons l’initialisation de la variable de classe NombreDInstance également dans cette nouvelle méthode new:. La voici:
new: n  
  n > NombreMaxDElements  
    ifTrue: [self error: ’trop d’éléments’].  
    NombreDInstances == nil  
      ifTrue [NombreDInstances := 1].  
    NombreDInstances = NombreMaxDElements  
      ifTrue: [self error: ’trop d’instances’]  
      ifFalse: [NombreDInstances := NombreDInstances + 1  
                super new: n]
Notez l’utilisation de la pseudo-variable super dans nos deux définitions de new:. Dans les deux cas elle sert à demander à SQUEAK de lancer ses “vraies” procédures de création d’instances après avoir fait nos opérations de vérification!