# Programmer objets avec Smalltalk

Gilles Clavel, Nathalie Lopez, Luc Veillon

► To cite this version:

Gilles Clavel, Nathalie Lopez, Luc Veillon. Programmer objets avec Smalltalk. 1996. hal-05342511

HAL Id: hal-05342511

https://inria.hal.science/hal-05342511v1

Submitted on 2 Nov 2025

HAL is a multi-disciplinary open access
archive for the deposit and dissemination of sci-
entific research documents, whether they are pub-
lished or not. The documents may come from
teaching and research institutions in France or
abroad, or from public or private research centers.

L'archive ouverte pluridisciplinaire HAL, est
destinée au dépôt et à la diffusion de documents
scientifiques de niveau recherche, publiés ou non,
émanant des établissements d'enseignement et de
recherche français ou étrangers, des laboratoires
publics ou privés.

<figure><img src="image_1.png" /></figure>

Distributed under a Creative Commons Attribution 4.0 International License
Gilles Clavel • Nathalie Lopez
Luc Veillon

# Programmer
objets avec
Smalltalk

applications OS/2
Windows et AIX
avec IBM Smalltalk

2e édition actualisée et complétée

MASSON
# Programmer objets avec Smalltalk

## Applications OS/2, Windows et AIX, avec IBM Smalltalk
# CHEZ LE MÊME ÉDITEUR

## Des mêmes auteurs

COMPRENDRE ET UTILISER C++ POUR PROGRAMMER OBJET, par G. CLAVEL, I. TRILLAUD, L. VEILLON. *Collection MIM-Algorithmique, programmation*. 1994, 248 pages.

INTRODUCTION À LA PROGRAMMATION, par G. CLAVEL et J. BIONDI. *Collection MIM-Algorithmique, Programmation*.
Tome 2. — Structures des données. 1994, 3ᵉ tirage, 272 pages. Également publié en espagnol (1985) et en italien (1985).

## Dans la collection MIM

LE DÉVELOPPEMENT DE LOGICIEL EN C++, par R. WINDER. Traduit de la 2ᵉ édition anglaise par P.-Y. BONNETAIN. 1994, 568 pages.

CONCEPTION OBJET DES STRUCTURES DE DONNÉES. Réalisation en langage C, par B. QUÉMENT. 1992, 248 pages.

LE LANGAGE C, norme ANSI, par B.W. KERNIGHAN et D.M. RITCHIE. Traduit de l'anglais par J.-F. GROFF et E. MOTTIER. 1994, 2ᵉ édition, 4ᵉ tirage, 296 pages.

LE LANGAGE C, solutions aux exercices de la 2ᵉ édition de l'ouvrage de B.W. KERNIGHAN et D.M. RITCHIE, par C.L. TONDO et S.E. GIMPEL. Traduit de l'anglais par D. BERTIER. 1992, 2ᵉ édition, 2ᵉ tirage, 168 pages.

LA BIBLIOTHÈQUE C STANDARD, par P.J. PLAUGER. Traduit par I. SAINT-SAËNS. Coédition avec Prentice Hall. 1995, 544 pages.

## Autres ouvrages

INGÉNIERIE DES OBJETS. Approche classe-relation, application à C++, par Ph. DESFRAY. *Méthodes Informatiques et Pratique des Systèmes*. 1993, 2ᵉ tirage, 248 pages.

L'APPROCHE OBJET. Concepts et techniques, par R. MOREAU. *Collection Méthodes Informatiques et Pratique des Systèmes*. 1994, 312 pages.

OMT, par J. RUMBAUGH, M. BLAHA, W. PREMERLANI, F. EDDY et W. LORENSEN. Coédition
Prentice Hall.
Tome 1. — MODÉLISATION ET CONCEPTION ORIENTÉES OBJET. Édition française revue et aug-
mentée, compléments sur la 2ᵉ génération de la méthode. Traduit de l'anglais par A.-B. FON-
TAINE, G.-P. REICH et V. ZAIM. 1995, 3ᵉ tirage, 536 pages.
Tome 2. — SOLUTION DES EXERCICES. Traduit de l'anglais par A.-B. FONTAINE et V. ZAIM.
1996, 264 pages.
# Programmer objets
avec Smalltalk

Applications OS/2, Windows
et AIX, avec IBM Smalltalk

Gilles CLAVEL

Directeur consultant de la société IMA-informatique
Professeur à l'Institut national agronomique

Nathalie LOPEZ

Ingénieur de la société IMA-informatique
Responsable des formations méthodes objet

Luc VEILLON

Chargé de recherches à l'ORSTOM
Responsable informatique du laboratoire ERMES

2ᵉ édition actualisée et complétée
<figure><img src="image_2.png" /></figure>

Ce logo a pour objet d'alerter le lecteur sur la menace que représente pour l'avenir de l'écrit, tout particulièrement dans le domaine universitaire, le développement massif du « photo-copillage ».

Cette pratique qui s'est généralisée, notamment dans les établissements d'enseignement, provoque une baisse brutale des achats de livres, au point que la possibilité même pour les auteurs de créer des œuvres nouvelles et de les faire éditer correctement est aujourd'hui menacée.

Nous rappelons donc que la reproduction et la vente sans autorisation, ainsi que le recel, sont passibles de poursuites. Les demandes d'autorisation de photocopier doivent être adressées à l'éditeur ou au Centre français d'exploitation du droit de copie : 3, rue Hautefeuille, 75006 Paris. Tél.: 43 26 95 35.

Extraits : © Digitalk, Inc., 1991. Tous droits réservés.

Cet ouvrage est la 2ᵉ édition de Découvrir la programmation orientée objets avec Smalltalk V par G. Clavel et L. Veillon.

Tous droits de traduction, d'adaptation et de reproduction par tous procédés, réservés pour tous pays.

Toute reproduction ou représentation intégrale ou partielle, par quelque procédé que ce soit des pages publiées dans le présent ouvrage, faite sans l'autorisation de l'éditeur, est illicite et constitue une contrefaçon. Seules sont autorisées, d'une part, les reproductions strictement réservées à l'usage privé du copiste et non destinées à une utilisation collective, et d'autre part, les courtes citations justifiées par le caractère scientifique ou d'information de l'œuvre dans laquelle elles sont incorporées (art. L. 122-4. L. 122-5 et L. 335-2 du Code de la propriété intellectuelle).

© Masson, Paris, 1991, 1996

ISBN : 2-225-85157-3

ISSN : 0249-6992
# Table des matières

<table><tr><td>Avant-propos</td><td>IX</td></tr></table>

## 1 - Objets, messages, classes et méthodes

1.1 - Des objets et des messages.................................................................................. 1

1.2 - Evaluer une séquence d'expressions........................................................................ 2

1.3 - Premier contact avec l'interface-utilisateur de Smalltalk........................................ 5

1.4 - Classes et méthodes........................................................................................................ 7

1.5 - Classes et instanciation.................................................................................................. 10

1.6 - Hiérarchie des classes et héritage.................................................................................. 12

1.7 - Héritage et polymorphisme.................................................................................................. 14

1.8 - Les outils de Smalltalk pour la manipulation des classes .................................... 17

## 2 - Le langage Smalltalk

2.1 - Les expressions.................................................................................................................. 21

2.2 - Les constantes littérales.................................................................................................. 21

2.2.1 - Les constantes numériques .................................................................................. 22

2.2.2 - Les constantes-chaînes .......................................................................................... 22

2.2.3 - Les constantes-caractères .................................................................................. 22

2.2.4 - Symboles et constantes-synboles ........................................................................ 22

2.2.5 - Les tableaux.................................................................................................................. 23

2.3 - Les séquences d'expressions........................................................................................ 24

2.4 - Les règles de priorité pour l'évaluation d'une expression .................................... 25

2.4.1 - Messages unaires........................................................................................................ 25

2.4.2 - Messages binaires........................................................................................................ 25

2.4.3 - Messages à mot(s)-clé(s) .................................................................................... 25

2.4.4 - Les priorités.................................................................................................................. 26

2.6 - Les variables d'instance.................................................................................................... 27

2.7 - Gestion dynamique de la mémoire disponible ............................................................ 30

2.8 - Les variables temporaires.................................................................................................. 33

2.8.1 - Les variables temporaires d'une séquence d'expressions ................................. 33

2.8.2 - Les arguments d'un bloc .......................................................................................... 33

2.8.3 - Les arguments d'une méthode.................................................................................. 35

2.9 - Les variables partagées.................................................................................................... 35

2.9.1 - Les variables globales .................................................................................................. 36

2.9.2 - Les dictionnaires partagés .................................................................................... 37

2.9.3 - Les variables de classe .......................................................................................... 38
<table><tr><td>2.10</td><td>La classe Context et l'évaluation d'un bloc</td><td>39</td></tr><tr><td>2.10.1</td><td>Un bloc est un objet</td><td>40</td></tr><tr><td>2.10.2</td><td>Bloc récursif</td><td>41</td></tr><tr><td>2.10.3</td><td>Blocs avec arguments</td><td>41</td></tr><tr><td>2.11</td><td>Les classes True et False et la réalisation d'un choix</td><td>42</td></tr><tr><td>2.11.1</td><td>Schémas conditionnels</td><td>42</td></tr><tr><td>2.11.2</td><td>Opérateurs logiques</td><td>43</td></tr><tr><td>2.12</td><td>La classe Context et les itérations</td><td>44</td></tr></table>

### 3 - Aller plus loin avec Smalltalk

<table><tr><td>3.1</td><td>Faire des calculs</td><td>47</td></tr><tr><td>3.1.1</td><td>La représentation littérale des nombres</td><td>48</td></tr><tr><td>3.1.2</td><td>Entiers, rationnels et réels approchés</td><td>48</td></tr><tr><td>3.1.3</td><td>Inspection d'un objet</td><td>50</td></tr><tr><td>3.1.4</td><td>La classe Float</td><td>51</td></tr><tr><td>3.1.5</td><td>Opérations arithmétiques élémentaires</td><td>53</td></tr><tr><td>3.1.6</td><td>Les intervalles</td><td>55</td></tr><tr><td>3.2</td><td>Les tableaux</td><td>56</td></tr><tr><td>3.2.1</td><td>Variables et tableaux</td><td>56</td></tr><tr><td>3.2.2</td><td>Un exemple d'utilisation des tableaux : le calcul matriciel</td><td>59</td></tr><tr><td>3.3</td><td>Les chaînes de caractères</td><td>65</td></tr><tr><td>3.3.1</td><td>Quelques manipulations de chaînes de caractères</td><td>66</td></tr><tr><td>3.3.2</td><td>Problèmes d'héritage liés à la classe String</td><td>68</td></tr><tr><td>3.3.3</td><td>Les arguments d'une méthode : variables ou objets ?</td><td>72</td></tr><tr><td>3.4</td><td>Créer une classe, sauvegarder et récupérer ses méthodes</td><td>74</td></tr><tr><td>3.4.1</td><td>Quel type de classe créer ?</td><td>74</td></tr><tr><td>3.4.2</td><td>Comment sauvegarder une classe et ses méthodes ?</td><td>77</td></tr><tr><td>3.4.3</td><td>Restaurer une classe</td><td>77</td></tr><tr><td>3.5</td><td>Les dictionnaires de Smalltalk</td><td>78</td></tr><tr><td>3.5.1</td><td>La classe Association</td><td>78</td></tr><tr><td>3.5.2</td><td>Un tableau pour enregistrer des associations</td><td>79</td></tr><tr><td>3.5.3</td><td>Un dictionnaire pour enregistrer des associations</td><td>81</td></tr><tr><td>3.5.4</td><td>Un exemple de dictionnaire analogique</td><td>82</td></tr><tr><td>3.5.5</td><td>La variable super</td><td>85</td></tr><tr><td>3.5.6</td><td>Enrichir le dictionnaire des analogies</td><td>87</td></tr><tr><td>3.5.7</td><td>Le dictionnaire de Smalltalk</td><td>90</td></tr></table>

### 4 - La classe Collection et sa descendance

<table><tr><td>4.1</td><td>Collection et ses sous-classes</td><td>95</td></tr><tr><td>4.2</td><td>La classe Bag : aperçu de quelques méthodes</td><td>97</td></tr><tr><td>4.2.1</td><td>Créer une instance de la classe Bag</td><td>97</td></tr><tr><td>4.2.2</td><td>Remplir une instance de la classe Bag</td><td>97</td></tr><tr><td>4.2.3</td><td>Explorer le contenu d'une instance de la classe Bag</td><td>98</td></tr><tr><td>4.3</td><td>Les classes Set et Dictionary</td><td>100</td></tr><tr><td>4.3.1</td><td>Des méthodes de conversion présentes dans Collection</td><td>100</td></tr><tr><td>4.3.2</td><td>Une sous-classe importante de Set : Dictionary</td><td>101</td></tr><tr><td>4.3.3</td><td>Les méthodes de Dictionary classées par fonctions</td><td>101</td></tr></table>
<table><tr><td>4.3.4</td><td>Un exemple d'utilisation de dictionnaire : la paye du personnel</td><td>103</td></tr><tr><td>4.4</td><td>Classe IndexedCollection</td><td>105</td></tr><tr><td>4.4.1</td><td>Quelques méthodes pour manipuler les index</td><td>106</td></tr><tr><td>4.4.2</td><td>Traiter la collection (méthodes définies dans IndexedCollection)</td><td>106</td></tr><tr><td>4.4.3</td><td>Modifier des objets de la collection</td><td>107</td></tr><tr><td>4.4.4</td><td>Copier la collection</td><td>107</td></tr><tr><td>4.4.5</td><td>Effectuer des traitements sur tous les objets d'une collection</td><td>107</td></tr><tr><td>4.5</td><td>La classe FixedSizeCollection</td><td>108</td></tr><tr><td>4.6</td><td>La classe OrderedCollection</td><td>113</td></tr><tr><td>4.6.1</td><td>Principales méthodes de OrderedCollection</td><td>114</td></tr><tr><td>4.6.2</td><td>Un exemple d'utilisation de OrderedCollection : une salle d'attente</td><td>115</td></tr><tr><td>4.6.3</td><td>La classe SortedCollection</td><td>117</td></tr><tr><td>4.7</td><td>La classe Bag et ses méthodes</td><td>118</td></tr><tr><td>4.7.1</td><td>Redéfinition des méthodes inadéquates pour la classe Bag</td><td>118</td></tr><tr><td>4.7.2</td><td>Initialisation de la classe Bag</td><td>119</td></tr><tr><td>4.7.3</td><td>La variable d'instance elements</td><td>120</td></tr><tr><td>4.8</td><td>Méthodes directement définies dans la classe Collection</td><td>121</td></tr><tr><td>4.8.1</td><td>Ajouter un objet dans une collection</td><td>121</td></tr><tr><td>4.8.2</td><td>Passer d'une sous-classe de Collection à une autre</td><td>122</td></tr><tr><td>4.9</td><td>Quelques méthodes de Set</td><td>123</td></tr><tr><td>4.9.1</td><td>Ajouter un objet dans une instance de Set</td><td>123</td></tr><tr><td>4.9.2</td><td>Stockage des objets dans une instance de Set : notion de hachage</td><td>124</td></tr><tr><td>4.9.3</td><td>Croissance d'une instance de Set</td><td>128</td></tr><tr><td>4.10</td><td>La classe Dictionary et ses sous-classes</td><td>130</td></tr><tr><td>4.10.1</td><td>Ajouter des éléments à un dictionnaire</td><td>130</td></tr><tr><td>4.10.2</td><td>Limiter les redéfinitions de méthodes dans une sous-classe : l'exemple de select et reject</td><td>131</td></tr><tr><td>4.10.3</td><td>La classe IdentityDictionary : un dictionnaire un peu particulier</td><td>132</td></tr><tr><td>4.11</td><td>La classe IndexedCollection</td><td>134</td></tr><tr><td>4.11.1</td><td>La classe IndexedCollection</td><td>134</td></tr><tr><td>4.11.2</td><td>La classe FixedSizeCollection</td><td>135</td></tr><tr><td>4.11.3</td><td>La classe OrderedCollection</td><td>135</td></tr><tr><td>4.11.4</td><td>La classe SortedCollection</td><td>136</td></tr></table>

## 5 - La classe Stream et sa descendance

<table><tr><td>5.1</td><td>Premiers pas dans la classe Stream</td><td>137</td></tr><tr><td>5.1.1</td><td>Qu'est-ce qu'un flux ?</td><td>138</td></tr><tr><td>5.1.2</td><td>Pourquoi une classe réservée aux flux ?</td><td>138</td></tr><tr><td>5.1.3</td><td>Opérations de transferts</td><td>139</td></tr><tr><td>5.1.4</td><td>Ouvrir un flux</td><td>140</td></tr><tr><td>5.2</td><td>Utiliser une instance de ReadStream</td><td>141</td></tr><tr><td>5.2.1</td><td>Comment créer une instance de ReadStream ?</td><td>141</td></tr><tr><td>5.2.2</td><td>Parcourir une collection avec un flux</td><td>142</td></tr><tr><td>5.2.3</td><td>Découper une collection en sous-collections avec un flux</td><td>143</td></tr></table>
<table><tr><td>5.2.4</td><td>Tester la présence d'un objet sans déplacer la position courante</td><td>144</td></tr><tr><td>5.2.5</td><td>Parcourir une collection de repère en repère</td><td>145</td></tr><tr><td>5.2.6</td><td>Accès direct dans une collection</td><td>146</td></tr><tr><td>5.3</td><td>Les sous-classes WriteStream et ReadWriteStream</td><td>146</td></tr><tr><td>5.3.1</td><td>Différences entre les sous-classes de Stream</td><td>146</td></tr><tr><td>5.3.2</td><td>Ouvrir un flux en écriture</td><td>147</td></tr><tr><td>5.3.3</td><td>La variable d'instance collection et son emploi selon les sous-classes de Stream</td><td>149</td></tr><tr><td>5.3.4</td><td>Les variables d'instance de position</td><td>151</td></tr><tr><td>5.4</td><td>Travailler sur un fichier avec FileStream</td><td>153</td></tr><tr><td>5.4.1</td><td>Comment accéder à un fichier sous Smalltalk ?</td><td>153</td></tr><tr><td>5.4.2</td><td>Copier un fichier existant</td><td>156</td></tr><tr><td>5.4.3</td><td>Manipuler des octets</td><td>160</td></tr></table>

## 6 - Réaliser une application avec IBM Smalltalk

<table><tr><td>6.1</td><td>Quelle application ?</td><td>167</td></tr><tr><td>6.1.1</td><td>Le cahier des charges</td><td>167</td></tr><tr><td>6.1.2</td><td>La structure des fichiers-dictionnaires</td><td>168</td></tr><tr><td>6.2</td><td>Une classe, comme support de l'application</td><td>169</td></tr><tr><td>6.2.1</td><td>La nouvelle variable d'instance et l'héritage</td><td>169</td></tr><tr><td>6.2.2</td><td>L'interface avec les fichiers</td><td>171</td></tr><tr><td>6.2.3</td><td>L'environnement objets facilite la programmation incrémentale</td><td>174</td></tr><tr><td>6.3</td><td>L'interface graphique de l'application</td><td>177</td></tr><tr><td>6.3.1</td><td>Faire apparaître une fenêtre dédiée à l'application</td><td>177</td></tr><tr><td>6.3.2</td><td>Les différentes classes de fenêtres</td><td>179</td></tr><tr><td>6.3.3</td><td>Faire apparaître des informations dans la fenêtre</td><td>180</td></tr><tr><td>6.3.4</td><td>Gérer un menu pour la fenêtre</td><td>182</td></tr><tr><td>6.4</td><td>Structurer la fenêtre de l'application</td><td>186</td></tr><tr><td>6.4.1</td><td>Une nouvelle classe pour l'application</td><td>187</td></tr><tr><td>6.4.2</td><td>Le schéma Model/View/Controller</td><td>188</td></tr><tr><td>6.4.3</td><td>Créer une instance de la classe ConsulteDico</td><td>189</td></tr><tr><td>6.4.4</td><td>Partager la fenêtre en plusieurs panneaux</td><td>190</td></tr><tr><td>6.4.5</td><td>Synchroniser les panneaux</td><td>192</td></tr><tr><td>6.4.6</td><td>Améliorer la synchronisation</td><td>193</td></tr><tr><td>6.5</td><td>L'application finale</td><td>193</td></tr><tr><td>6.5.1</td><td>Les nouvelles variables d'instance de la classe ConsulteDico</td><td>195</td></tr><tr><td>6.5.2</td><td>Des variables d'instance pour désigner les panneaux</td><td>196</td></tr><tr><td>6.5.3</td><td>La version finale de la méthode ouvrirFenetre</td><td>197</td></tr><tr><td>6.5.4</td><td>Effet d'une sélection dans le panneau droit</td><td>198</td></tr><tr><td>6.5.5</td><td>Le menu des panneaux verticaux</td><td>201</td></tr></table>

Annexe - utilisisation d'un dictionnaire analogique : l'application 203

Bibliographie 217

Index 219
# Avant-propos

Voici un ouvrage pratique, sur un thème d'actualité. Notre précédent livre de 1991 anticipait une évolution que les dernières années ont largement confirmée : l'objet est aujourd'hui au cœur de l'industrie du logiciel. Dans ce nouvel ouvrage nous expliquons les mécanismes de l'approche objet à travers Smalltalk, le langage qui en a le premier formalisé les concepts. Ce langage lui aussi fait partie de l'actualité : la concurrence entre ses différentes implémentations le montre bien.

Réaliser un ouvrage sur Smalltalk, en s'appuyant sur deux implémentations différentes, est sans doute un exercice délicat. Si nous avons choisi de le faire, c'est pour montrer que le langage est désormais suffisamment uniforme pour que sa présentation ne dépende pas d'une implémentation particulière. D'une version à l'autre, les principales différences concernent la programmation d'une interface graphique, étudiée ici au chapitre 6 avec IBM Smalltalk.

Par rapport à 1991, date de notre premier livre, la situation industrielle est bien différente. Smalltalk ne sert plus uniquement à illustrer les concepts objets. Il est désormais un outil reconnu pour le développement de logiciel. Le fait que le marché soit très disputé entre trois éditeurs (dont deux viennent de décider de se rapprocher) en est une preuve. La majeure partie des caractéristiques du langage est commune aux trois implémentations. Nous les présentons dans les cinq premiers chapitres avec des exemples en Smalltalk V et IBM Smalltalk. Pour l'étude de l'interface graphique, chaque éditeur a choisi une implémentation différente et nous avons été contraints de faire un choix, en retenant celle d'IBM Smalltalk.

Construit comme une découverte progressive, cet ouvrage introduit les concepts en s'appuyant à chaque fois sur des exemples pratiques. Notre expérience de formateurs nous a montré que cette approche était efficace. Le livre s'articule en trois parties :

- Les trois premiers chapitres sont consacrés à la découverte des concepts de base, présentés à travers Smalltalk, mais que l'on pourra retrouver dans la plupart des environnements objets.

- Les deux chapitres suivants approfondissent ces concepts, en expliquant les mécanismes internes mis en jeu et en s'appuyant sur l'architecture propre à Smalltalk.

- La dernière partie montre la facilité d'emploi et la grande simplicité d'un langage objets pour construire des applications avec une interface graphique.
Nous souhaitons que le lecteur prenne autant de plaisir que nous en avons trouvé à programmer objets avec Smalltalk. De tous les langages informatiques que nous connaissons, Smalltalk est sans doute le plus gratifiant, grâce à la communication qu'il installe entre le programmeur et son application. Ailleurs, des variables subissent des traitements ; ici des objets répondent ou refusent des messages : la mise au point d'un programme s'apparente plus à une conversation qu'à une laborieuse recherche d'erreurs. Un autre aspect de la programmation objets est le confort qu'elle offre pour programmer aussi bien de manière ascendant que descendante. Et là est sans doute une des raisons de son succès croissant : voilà enfin une approche qui permet d'obtenir des programmes capables d'évoluer. Car nous sommes convaincus, pour reprendre littéralement l'expression de Brooks, que « *programs are grown, not built* ».

Nous voulons ici remercier tous ceux qui nous ont aidés lors de la rédaction de cet ouvrage. La société IMA-informatique a fourni le support logistique pour la réalisation technique et matérielle du manuscrit. Son concours nous a été précieux et nous lui en sommes vivement reconnaissants. Nous exprimons aussi notre gratitude à la société IBM-France qui a soutenu efficacement notre projet. Nous remercions également Christian Burgei qui a développé en Smalltalk le logiciel qui a permis de réaliser l'index de ce livre et qui a patiemment validé le texte de cet index. Enfin, nous ne saurions oublier Frédérique Lauque qui avait assuré la composition du livre de 1991 : la qualité de son travail nous a grandement facilité la reprise de ceux des textes de 1991 qui étaient restés actuels.

Gilles Clavel, Nathalie Lopez et Luc Veillon

Paris - Orléans, juillet 1995
# 1 - Objets, messages, classes et méthodes

## 1.1 - Des objets et des messages

Pour celui qui l'aborde en ayant toujours pratiqué des langages classiques, Smalltalk est un peu surprenant. Les expressions que Smalltalk est amené à évauluer dans l'exécution d'une application ont la plupart du temps une forme inhabituelle. Considérons par exemple les expressions du langage Smalltalk qui sont représentées à la figure suivante.

```
5 + 3
'Alexandre' size
'abcde' reverse
3 between: 1 and: 5
'voici une petite phrase' subStrings size
```

<caption>Figure 1.1 : quelques expressions du langage</caption>

Si la première d'entre elles a, pour tous les programmeurs une allure familière, elle n'en est pas moins interprétée par Smalltalk d'une manière particulière. Pour Smalltalk en effet, cette expression fait intervenir deux *objets* (les entiers 5 et 3) et un *message* (+). Les deux objets n'ont pas des rôles symétriques : l'un d'entre eux est *actif* : l'objet 5 reçoit le message et y répond. Sa réponse sera le résultat de l'évaluation de l'expression. L'autre objet (3) a un rôle *passif* : il est le paramètre ou l'argument du message +.

« Que de complications pour une écriture qui, dans un langage comme C ou Pascal, s'évaluera 8 » sera tenté de dire le lecteur. Rassurons-le tout d'abord sur le résultat de l'évaluation par Smalltalk : il sera identique à celui obtenu avec un langage classique. Demandons-lui ensuite un peu de recul et de la patience : l'informatique est une discipline qui a vite fait d'enfermer l'utilisateur dans une habitude hors de laquelle celui-ci portera souvent un jugement a priori défavorable sur tout ce qui peut le déranger de cette habitude. Examinons les autres exemples de la figure 1.1, pour nous habituer au dérangement.
### 'Alexandre' size

L'objet qui reçoit le message figure toujours au début de l'expression : il s'agit de la chaîne de caractères 'Alexandre' qui reçoit le message size. En réponse, l'objet communique sa taille, c'est-à-dire son nombre de caractères. Le résultat de l'évaluation de Smalltalk est donc ici 9. Notons que le message size n'a pas d'argument : c'est un message unaire. Dans l'exemple précédent, le message + utilise un argument. C'est un message binaire.

### 'abcde' reverse

L'évaluation produira la chaîne 'edcba'. En effet, le message unaire reverse demande à l'objet 'abcde' de renvoyer l'objet qui est obtenu en énumérant les caractères dans l'ordre inverse.

### 3 between: 1 and: 5

Ici, l'objet 3 reçoit le message à mot-clés between:and: qui est un message à deux arguments. Dans un tel message, chaque mot-clé se termine obligatoirement par le signe : et doit être suivi par un argument. Ce message demande à l'objet 3 de dire s'il se classe entre 1 et 5. L'évaluation par Smalltalk donne la réponse de l'objet 3 : true. Cette dernière valeur est l'objet du langage Smalltalk qui représente la valeur logique vrai.

Notons, à propos de tous les exemples précédents, que la réponse d'un objet à un message est toujours un autre objet : 8, 9, 'edcba' et true sont eux-mêmes des objets qui, à leur tour, peuvent recevoir des messages, ce qu'illustre le dernier exemple de la figure 1.1 :

### 'voici une petite phrase' subStrings size

Cette expression comprend un objet (la chaîne 'voici une petite phrase') et deux messages unaires (subStrings et size). Pour suivre l'évaluation qui sera faite par Smalltalk, il est nécessaire de savoir que, dans une succession de deux messages unaires, le premier est traité avant le second et que, si $o_1$ est un objet et $m_1$ et $m_2$ deux messages unaires, l'expression $o_1 m_1 m_2$ s'évaluera de la manière suivante :

- l'objet $o_1$ reçoit le message $m_1$ et renvoie en réponse un objet $o_2$;

- le nouvel objet $o_2$ reçoit le message $m_2$ et renvoie en réponse un objet $o_3$.

Dans notre exemple, la chaîne 'voici une petite phrase' reçoit le message subStrings et répond en renvoyant un tableau dont chaque élément est un mot de la chaîne. Ce tableau reçoit ensuite le message size et répond en indiquant sa dimension : il renvoie donc l'objet 4.

## 1.2 - Evaluer une séquence d'expressions

Examinons la suite d'expressions du langage Smalltalk présentée à la figure 1.2. Pour en faciliter la lecture, nous l'avons écrite sur quatre lignes. Les trois dernières sont des expressions que Smalltalk devra évaluer. Conformément aux règles d'écriture du langage, chaque expression qui n'est pas la dernière est suivie d'un point qui joue le rôle de séparateur. La première ligne de la séquence n'est
pas une expression à évaluer : elle représente une déclaration de deux variables *chaine* et *chaineInverse*. Ces deux variables sont des variables temporaires : elles seront créées dans l'environnement de Smalltalk au début de l'évaluation de la séquence et elles disparaîtront de cet environnement à la fin de cette évaluation.

Avant d'étudier le mécanisme de l'évaluation de la séquence de la figure 1.2, précisons quel est le contexte de l'utilisateur : comment pourra-t-on demander à Smalltalk l'évaluation de cette séquence ?

On utilise généralement Smalltalk par l'intermédiaire d'un poste de travail clavier/écran disposant d'une souris. A l'écran, dans l'environnement de travail de Smalltalk, on peut faire apparaître une ou plusieurs fenêtres et, parmi celles-ci, il y en a toujours au moins une qui est une fenêtre d'édition de texte : on peut aisément y enregistrer la saisie de la séquence de la figure 1.2 puis, à l'aide de la souris, on peut sélectionner le texte complet de cette séquence. Le texte sélectionné apparaît alors à l'écran en inverse vidéo. Ensuite on active le menu de la fenêtre dans lequel on choisira l'option *Execute*. En réponse, Smalltalk effectuera l'évaluation du texte sélectionné. Pour préciser la manière d'effectuer ces manipulations, des indications supplémentaires seront données, avec des exemples, au paragraphe suivant (1.3).

```
|chaine chaineInverse|
chaine := 'abcde'.
chaineInverse := chaine reverse.
Transcript nextPutAll: chaineInverse
```

<caption>Figure 1.2 : une première séquence d'expressions</caption>

Revenons à l'évaluation de la séquence de la figure 1.2. La première expression évaluée :

```
chaine := 'abcde'
```

affecte à la variable *chaine* l'objet 'abcde'. Plutôt que d'évoquer le mécanisme d'affectation classique des langages usuels, on peut considérer que l'opération revient à donner le nom *chaine* à l'objet 'abcde' ce qui nous permet, une variable étant elle-même un objet, de parler désormais de l'objet *chaine*, dont la valeur est 'abcde". Ici, l'objet *chaine* est une variable temporaire : par convention, toute variable déclarée entre deux barres verticales (| et |) et dont le nom commence par une lettre minuscule est une variable temporaire, qui disparaîtra de l'environnement de Smalltalk, dès que la séquence dans laquelle elle a été déclarée aura été évaluée. On peut également déclarer des variables globales en leur choisissant un nom dont le premier caractère est une lettre majuscule. Une variable globale fera partie de l'environnement permanent de Smalltalk (et sera sauvegardée, avec cet environnement, à chaque fin d'exécution de Smalltalk). Une variable globale est donc permanente : elle ne disparaît que si l'utilisateur

<footnote n="1">1 En fait, Smalltalk va réaliser de manière interne l'affectation en faisant pointer l'objet sur la valeur affectée (cf chapitre 2, paragraphe 2.7).</footnote>
prend la décision de la supprimer de l'environnement Smalltalk (cf. chapitre 2, paragraphe 2.9).

Toujours à propos des variables, remarquons que, lorsque le processeur Smalltalk évalue, dans le début de la séquence, les deux lignes :

```
|chaine chaineInverse|
chaine := 'abcde'.
```

les opérations effectuées sont les suivantes :

- à l'issue du traitement de la première ligne, le processeur Smalltalk a créé les deux variables *chaine* et *chaineInverse* ; elles désignent toutes deux l'objet *nil*, qui sert à représenter toute valeur indéterminée.

- Après l'évaluation de la deuxième ligne, *chaine* représente l'objet 'abcde' tandis que *chaineInverse* désigne toujours la valeur *nil*.

L'évaluation de la troisième ligne de la figure 1.2 va d'abord envoyer, à l'objet *chaine*, le message *reverse* (cf. 1.1). L'objet *chaine* va renvoyer en retour la chaîne 'edcba', qui va devenir la nouvelle valeur de la variable *chaineInverse*.

La dernière expression évaluée envoie le message *nextPutAll: chaineInverse* à l'objet *Transcript*. Le nom de cet objet commence par une majuscule : il s'agit donc d'un objet global, toujours présent dans l'environnement de Smalltalk. *Transcript* est l'éditeur de texte qui gère la fenêtre intitulée *System Transcript*. Cette fenêtre apparaît en permanence sur l'écran de l'environnement de travail de Smalltalk. La plupart des messages envoyés à l'utilisateur seront affichés dans cette fenêtre. L'utilisateur peut lui-même se servir de *Transcript* pour enregistrer et modifier du texte dans la fenêtre. Quand un objet-éditeur reçoit le message *nextPutAll*: avec une chaîne comme argument (*chaineInverse* sur la figure 1.2), la réponse de cet éditeur consiste à afficher la chaîne à l'écran à partir de la position du curseur. L'effet de la dernière évaluation de la séquence de la figure 1.2 est donc d'afficher, dans la fenêtre *System Transcript*, la chaîne 'edcba'.

Pour en terminer avec l'étude de la séquence de la figure 1.2, signalons que l'écriture, en Smalltalk, est relativement libre et que les expressions peuvent se succéder sur une même ligne, à la seule condition d'être séparées les unes des autres par des points. On aurait donc pu présenter toute la séquence sur une seule ligne (les lignes traitées par un objet-éditeur ne sont pas limitées en longueur). Enfin, pour obtenir le résultat décrit, il n'était pas nécessaire d'utiliser les variables *chaine* et *chaineInverse* et l'on aurait pu tout aussi bien demander l'évaluation de :

```
Transcript nextPutAll: 'abcde' reverse
```

ou encore, bien entendu :

```
Transcript nextPutAll: 'edcba'
```

Avant d'aller plus loin, précisons, à propos de l'affectation, la distinction qu'il faut faire entre les termes de variable et d'objet. Nous reviendrons au chapitre 2 sur le mécanisme interne de l'opération d'affectation mais, dès à présent, indiquons que, quand nous « affectons » l'objet *o* à la variable *v*, avec l'évaluation de :

```
v := o
```

nous prenons ensuite la liberté de parler, par abus de langage, de l'objet *v* pour désigner la valeur de *v*, alors qu'il conviendrait plutôt de dire l'objet désigné par la
variable *v*. Cet abus de langage est bien compréhensible dans la mesure où, si on envoie après l'affectation le message *m* à la variable *v*, c'est l'objet *o* (valeur de *v*) qui répondra à ce message.

Pour terminer, remarquons enfin que, dans cet ouvrage, nous utiliserons systématiquement le symbole $:=$ pour noter l'affectation, conformément à la notation utilisée en IBM Smalltalk et Smalltalk V (Smalltalk 80 utilise le symbole $\leftarrow$).

## 1.3 - Premier contact avec l'interface-utilisateur de Smalltalk

Précisons maintenant quel est l'interface-utilisateur de Smalltalk. Quand on travaille avec Smalltalk, on peut faire apparaître une ou plusieurs fenêtres à l'écran et, à chaque instant, l'une d'entre elles est la fenêtre active. La figure 1.3 présente un tel écran sur lequel apparaissent deux fenêtres.

<figure><img src="image_3.png" /><figcaption>Figure 1.3 : deux fenêtres à l'écran de l'environnement Smalltalk</figcaption></figure>

Sur cet écran, la fenêtre *System Transcript* apparaît en arrière plan et sa barre de titre est en caractères noirs sur fond blanc : ce n'est pas la fenêtre active. En revanche, la fenêtre *Workspace* est au premier plan et sa barre de titre est affichée en inversion vidéo : c'est la fenêtre active. Dans cette fenêtre, le texte `'Alexandre size'` : est sélectionné : il apparaît en inversion vidéo. Avec cette sélection, si l'utilisateur tape un caractère dans la fenêtre, ce caractère fera disparaître la sélection et viendra se placer à l'endroit qui était occupé par le premier caractère de cette sélection. Un des premiers usages possibles de la sélection est donc l'effaçement du texte sélectionné<fnref n="2" />.

<footnote n="2">2 On peut aussi effacer la sélection sans la remplacer par un caractère, en appuyant sur la touche d'effacement, immédiatement après avoir fait la sélection. Nous reviendrons au chapitre 3 sur les manipulations élémentaires de l'interface graphique et en particulier sur les mécanismes de copie et suppression liés à la sélection.</footnote>
Etudions maintenant l'utilisation de la sélection d'une expression pour déclencher une évaluation. Si nous revenons à la figure 1.3, où le texte 'Alexandre size' est sélectionné dans la fenêtre *Workspace* et si nous cliquons, dans cette fenêtre, avec le bouton *droit* de la souris, nous faisons apparaître le menu général de l'édition de texte qui est présenté sur la fenêtre de gauche de la figure 1.4.

<figure><img src="image_4.png" /><figcaption>Figure 1.4 : évaluation d'une expression avec l'option *Display*</figcaption></figure>

Cette figure ne représente pas un écran de Smalltalk, mais deux états successifs de la fenêtre *Workspace*. La partie gauche de la figure donne la représentation de cette fenêtre après l'apparition du menu. Deux des options de ce menu permettent l'évaluation :

*Display* le choix de cette option déclenchera l'évaluation de la séquence sélectionnée et demandera à Smalltalk d'afficher, dans la fenêtre, immédiatement après la sélection, la dernière valeur renvoyée par l'évaluation.

*Execute* le choix de cette option déclenchera l'évaluation de la séquence sélectionnée sans affichage de la dernière valeur renvoyée.

Examinons la figure 1.4. Dans la fenêtre de gauche, la position du curseur sélectionne l'option *Display*, qui apparaît en inversion vidéo. Si l'on clique alors avec le bouton gauche de la souris, on déclenche l'évaluation et le résultat est représenté dans la partie droite de la figure. La conséquence visible de l'évaluation est double :

- l'expression qui était sélectionnée ne l'est plus : elle apparaît désormais en caractères noirs sur fond blanc ;

- la valeur résultant de l'évaluation est l'entier 9 (longueur de la chaîne 'Alexandre'); elle apparaît sous la forme d'une nouvelle sélection qui suit immédiatement la dernière position de la sélection antérieure.
Pour terminer, revenons sur l'option *Execute*. Comme l'option *Display*, elle déclenche l'évaluation mais elle ne provoque pas l'affichage de la dernière valeur renvoyée par cette évaluation. On l'utilisera donc chaque fois que l'évaluation est faite pour obtenir un résultat qui n'est pas représenté par la dernière valeur renvoyée. En effet, de nombreux messages provoquent, de la part de l'objet qui les reçoit, une réaction qui ne peut être réduite au renvoi d'une valeur.

Par exemple, le message `nextPutAll: uneChaîne` reçu par l'objet `Transcript` entraînera l'apparition de `uneChaîne`, sur la fenêtre `System Transcript`, à l'emplACEMENT du curseur.

On notera aussi qu'en réponse au message `nextPutAll:`, l'objet récepteur renvoie également sa propre valeur. Si donc on saisit, dans la fenêtre `Workspace`, l'expression :

Transcript nextPutAll: 'Bonjour'

et si, après avoir sélectionné cette expression, on en déclenche l'évaluation avec *Display*, on constatera deux modifications à l'écran :

- le mot *Bonjour* apparaîtra sur *System Transcript*, représentant ainsi la réaction de l'objet *Transcript* au message reçu,

- la locution *an EtTranscript* apparaîtra sur *Fenêtre de travail*, à la suite de l'expression évaluée. Cette locution désigne la catégorie des objets tels que *Transcript* (la *classe* de ces objets, cf. paragraphe suivant). Smalltalk indiquera ainsi, conformément au mode d'évaluation de l'option *Display*, que l'objet renvoyé en fin d'évaluation est un éditeur de texte.

En revanche, si on déclenche la même évaluation avec *Execute*, on obtiendra seulement l'apparition de *Bonjour* sur *System Transcript*.

Le mode d'évaluation *Execute* est donc à choisir quand le but de l'évaluation n'est pas l'examen de la dernière valeur renvoyée. A l'inverse, si l'on sélectionne une expression dont l'évaluation n'a pas d'autre effet que le retour de la valeur renvoyée par le récepteur, l'évaluation devra être faite avec *Display* pour faire apparaître le résultat. Ainsi, une évaluation avec *Execute* de 5+8 n'aura aucun effet visible à l'écran.

## 1.4 - Classes et méthodes

Nous avons vu que l'évaluation de `'abcde' reverse` renvoyait la chaîne `'edcba'`. Si nous essayons maintenant d'évaluer :

1234 reverse

nous obtenons, à l'écran, l'apparition d'une fenêtre de diagnostic qui indique la détection d'une erreur. Le libellé de l'erreur :

SmallInteger does not understand reverse

nous indique que l'objet `1234` n'a pas pu traiter le message *reverse*. Si donc un objet `x` « comprend » un message `m`, il n'en est pas forcément de même pour un autre objet `y`.
En fait, les objets sont répartis en *classes*, une classe rassemblant des objets ayant les mêmes propriétés. Dans Smalltalk, chaque classe définit les *messages* qui sont susceptibles d'être envoyés aux objets qui lui appartiennent : la définition d'un message décrit la *méthode* à appliquer (i.e. les expressions à évaluer) pour traiter ce message.

Ainsi, la classe *String*, à laquelle appartient l'objet 'abcde', « possède<fnref n="3" /> » la méthode correspondant au *sélecteur* (c'est ainsi qu'on désigne le mot-clé identifiant le message) *reverse*. En revanche, la classe *SmallInteger*, à laquelle appartient l'objet 1234 ne contient aucune méthode dont le sélecteur est *reverse*. Nous en arrivons à l'un des aspects fondamentaux de la programmation orientée objets. A la différence de la programmation classique, où les données sont décrites à côté des traitements, la définition d'une classe d'objets comprend à la fois la description des données qui composent chaque objet de la classe et les traitements susceptibles d'être appliqués à ces objets (i.e. les méthodes). L'utilisateur d'un objet n'a pas besoin de connaître les détails de la réalisation des méthodes applicables à cet objet : il lui suffit d'en connaître les modalités de fonctionnement.

L'environnement de la programmation orientée objets permet à l'utilisateur de compléter, chaque fois qu'il le désire, la description d'une classe en modifiant ou en ajoutant des méthodes. Définissons par exemple, pour la classe *SmallInteger* d'IBM Smalltalk, qui comprend les entiers relatifs codés sur 32 bits, la méthode correspondant au sélecteur *reverse*. Le code correspondant à cette nouvelle méthode est présenté à la figure 1.5.

Examinons les opérations effectuées par l'exécution de cette méthode. La première ligne énonce le sélecteur de la méthode (message unaire *reverse*). Les deux lignes suivantes représentent un commentaire qui décrit le traitement effectué par la méthode. En Smalltalk, un commentaire peut s'écrire librement, sur une ou plusieurs lignes, encadré par des guillemets " et ".

Après le commentaire, apparaît la déclaration des variables temporaires qui seront utilisées par la méthode :

*valeurAbsolue* prendra au départ la valeur absolue de l'objet récepteur ;

*signe* indiquera son signe (+1 pour plus, -1 pour moins) ;

*inversePositif* contiendra la valeur inversée de la valeur absolue ;

*chiffre* désignera chaque chiffre traité.

L'expression (*self* between: -9 and: 9) *ifTrue*: [^ *self*] représente une opération conditionnelle. Nous reviendrons en 2.11 sur l'évaluation d'une telle expression. Etudions-la brièvement. Le mot *self* est un mot réservé de Smalltalk qui sert, dans la description d'une méthode, à désigner l'objet récepteur du message correspondant à la méthode décrite (ici, la méthode *reverse*). Ainsi, l'évaluation de *self* between: -9 and: 9 amène Smalltalk à envoyer le message à mots-clés *between*: -9 and: 9 à *self*, c'est-à-dire à l'objet qui a reçu le message *reverse*. Si cet objet répond *true*, l'expression encadrée par des crochets, immédiatement après *ifTrue*:, est évaluée : c'est l'expression ^ *self*. Le symbole ^ indique à Smalltalk

<footnote n="3">³ En fait, comme nous le verrons plus loin (cf chapitre 5), ce n'est pas la classe *String* qui définit la méthode *reverse*, mais la classe *IndexedCollection*, dont *String* est une descendante. Ainsi, *String* hérite de la méthode *reverse*.</footnote>
qu'il doit renvoyer, comme réponse de la méthode, l'expression qui suit ce symbole. Une telle opération termine toujours l'exécution d'une méthode. Si donc l'objet récepteur est supérieur ou égal à -9 et inférieur ou égal à 9, la méthode se termine en envoyant cet objet comme réponse. Par exemple, l'évaluation de `8 reverse` renverra l'objet `8`.

### reverse

```
"renvoie l'entier obtenu en énumérant les chiffres du récepteur de la droite vers la gauche :
par exemple 123 reverse renvoie 321"

|inversePositif valeurAbsolue signe chiffre|
"si le récepteur n'a qu'un chiffre, on renvoie ce chiffre"
(self between: -9 and: 9) ifTrue: [^ self].
"ici, on est sûr que le récepteur a au moins deux chiffres"
valeurAbsolue := self abs. "abs renvoie la valeur absolue"
signe := self // valeurAbsolue. "//renvoie le quotient entier"
inversePositif := 0.
[valeurAbsolue > 0] whileTrue: "tant qu'il reste des chiffres à traiter"
    [chiffre := valeurAbsolue \\ 10. "on divise par 10 pour prendre
le chiffre de droite (\| renvoie le reste de la division entière)"
inversePositif := inversePositif * 10 + chiffre.
"on introduit ce chiffre dans inversePositif en décalant vers la gauche"
valeurAbsolue := valeurAbsolue // 10
"on divise par 10 afin de passer au chiffre suivant"
].
"fin de l'itération, il reste à renvoyer l'entier inverse avec un signe correct"
^ signe * inversePositif
```

<caption>Figure 1.5 : méthode reverse pour la classe `SmallInteger`</caption>

Si l'objet receveur n'est pas dans l'intervalle [-9, +9], l'expression `^ self` n'est pas évaluée et l'exécution de la méthode continue en affectant des valeurs initiales aux variables `valeurAbsolue`, `signe` et `inversePositif`. Par exemple, si l'expression évaluée est `-357 reverse`, l'objet-récepteur (`self`) est ici -357 et l'on affecte :

- l'objet `357` à `valeurAbsolue` (`self abs renvoie la valeur absolue de self`),

- l'objet `-1` à `signe` (`signe moins`),

- une valeur initiale nulle à `inversePositif`.

Ensuite, on aborde une itération dont la forme est :

`[prédicat] whileTrue: [expressions à évaluer tant que le prédicat est vrai]`

La forme générale des structures itératives sera étudiée au chapitre 2. Pour l'instant, le lecteur constatera aisément, en lisant le texte de la figure 1.5, que les évaluations répétées dans l'itération composent un nouvel entier dans la variable `inversePositif`. Cet entier est obtenu en prélevant les chiffres *par la droite* dans la valeur absolue de l'objet récepteur. Chaque chiffre ainsi obtenu est inséré avec un
décalage à gauche dans inversePositif. A la sortie de l'itération, on renvoie, en réponse, la valeur obtenue en lui attribuant le même signe que l'objet récepteur : ^signe * inversePositif.

Si nous ajoutons, à la classe SmallInteger<fnref n="4" />, la méthode reverse, telle qu'elle est définie dans la figure 1.5, nous pouvons alors envoyer à l'objet 1234 le message reverse et nous obtiendrons, en réponse, l'objet 4321. Mais si nous essayons ensuite d'évaluer :

1231231234 reverse

nous constatons un échec : une fenêtre de diagnostic apparaît et nous annonce que le message reverse n'a pas été « compris ». Le contenu de la fenêtre donne l'indication supplémentaire suivante :

LargeInteger does not understand reverse

Ce diagnostic signifie que le message reverse a été reçu par un objet de la classe LargeInteger, pour laquelle il est inconnu. En effet, nous avons défini ce message pour les objets de la classe SmallInteger alors que l'entier 1231231234 est de la classe LargeInteger. Un objet ne peut, en principe, accepter que les messages définis pour sa classe.

## 1.5 - Classes et instanciation

Revenons sur les notions d'objet, de classe et de méthode. L'un des objectifs de la programmation orientée objets est d'encapsuler, dans un objet, les données et les méthodes qui sont propres à cet objet : c'est l'objet lui-même qui est responsable du traitement des messages qui lui seront adressés. La notion de classe peut être rapprochée de celle de type : une classe définira les propriétés de tous les objets qui lui seront associés. Chaque objet est, en quelque sorte, une matérialisation de sa classe. Un objet peut exister de manière permanente, comme par exemple l'objet true de la classe True (cf. 2.11), mais, le plus souvent, il est créé par instanciation. Pour cela, un message est envoyé à la classe.

En effet, dans Smalltalk, tout est objet et une classe est elle-même un objet qui peut recevoir des messages. Parmi les messages que l'on peut adresser à une classe donnée, il y en a souvent plusieurs qui servent à créer un nouvel objet de cette classe. Lorsqu'une classe C reçoit un tel message, elle répond donc en général en renvoyant une nouvelle instance de la classe, c'est-à-dire un nouvel objet, qui est ajouté à l'environnement de Smalltalk. La figure 1.6 présente quelques exemples d'instanciations.

Dans cette figure, les objets créés sont affectés aux variables temporaires c1, c2, t, e, d et sac. On notera que, dans chacun des messages d'instanciation, l'objet récepteur est toujours la classe dont on veut créer une instance. La réponse de la classe au message est l'objet créé, qui est alors affecté à la variable temporaire correspondante. Les deux premiers messages sont adressés à la classe String et

<footnote n="4">4 Nous verrons, en 1.8, comment modifier, ajouter et supprimer des méthodes pour une classe donnée</footnote>
créent respectivement une chaîne vide (message *new*) et une chaîne de cinq caractères, de valeur indéterminée (message à mot-clé *new:5*).

Avec les deux exemples suivants, on constate que la même méthode *new*: est acceptée par les classes *Array* et *Set* pour créer un tableau et un ensemble.

L'évaluation suivante instancie un objet *date*. La classe *Date* définit les propriétés de ces objets. Elle répond au message *today* en renvoyant une instance qui représente la date d'aujourd'hui.

Enfin, l'évaluation de la dernière expression crée, avec la méthode *with:with:with:*, un objet de la classe *Bag*. Un tel objet (cf. chapitre 4) est un conteneur, dans lequel on peut ranger, sans ordre, des objets quelconques et qui peut, à la différence d'un ensemble, contenir plusieurs fois le même objet. Ici, l'objet créé *sac* contient trois objets : 'Durand' et 'Lille', de la classe *String* et 1945 de la classe *SmallInteger*.

```
|c1 c2 t e d sac|
c1 := String new. "c1 est une chaîne vide"
c2 := String new: 5.
"c2 est une chaîne de cinq caractères, de valeur indéterminée"
t := Array new: 10.
"t est un tableau de 10 éléments ; chaque élément a la valeur nil (UndefinedObject)"
e := Set new: 40.
"e est un ensemble pouvant accueillir jusqu'à 40 éléments ; actuellement e est vide"
d := Date today. "d est la date d'aujourd'hui"
sac := Bag with: 'Durand' with: 1945 with: 'Lille'.
"sac est un objet contenant trois valeurs"
```

<caption>Figure 1.6 : exemples d'instanciations</caption>

Une classe dispose donc en général d'une ou de plusieurs méthodes de classe qui représentent des messages qui peuvent être adressés à la classe (et non pas aux objets de cette classe). Ces messages peuvent avoir pour effet de créer un objet, nouvelle instance de cette classe.

Les objets de la classe peuvent, à leur tour, recevoir des messages qui correspondront à des méthodes d'instance. La méthode d'instance *m*, définie dans la classe *C*, décrit donc le traitement que doit effectuer tout objet *o*, instance de *C*, en réponse à un message de sélecteur *m*.

En principe, un message correspondant à une méthode d'instance *m*, définie pour la classe *C*, ne peut être accepté par un objet d'une autre classe *C'*, pour laquelle *m* n'a pas été définie. C'est ce que nous avons constaté avec le message *reverse* défini pour la classe *String* et que nous avons redéfini pour la classe *SmallInteger*. Cependant, pour éviter de définir plusieurs fois la même méthode, comme nous pourrions être tentés de le faire, avec la méthode *reverse*, pour les
classes *SmallInteger* et *LargeInteger*, il existe un mécanisme fondamental pour la programmation orientée objets : l'héritage.

Avant d'étudier l'héritage, au paragraphe suivant, illustrons les notions qui viennent d'être exposées, en examinant les exemples de la figure 1.7. Le lecteur comprendra aisément l'effet de l'évaluation de la séquence sachant que :

- le message *at: indice put: valeur* correspond à la méthode d'instance *at:put*: de la classe *Array* et affecte *valeur* à l'élément de rang *indice* du tableau récepteur;

- le message *printString* peut être envoyé à tout objet : il renvoie une représentation de cet objet sous forme d'une chaîne de caractères<fnref n="5" />. Ici, ce message est envoyé à l'objet *Array y* et la chaîne obtenue en réponse est fournie en argument d'un message *nextPutAll*: qui provoque l'apparition de cette chaîne sur la fenêtre *Transcript*.

<figure><img src="image_5.png" /><figcaption>Figure 1.7 : méthodes de classe et d'instance (exemples de messages)</figcaption></figure>

## 1.6 - Hiérarchie des classes et héritage

Mettons en évidence les mécanismes d'héritage, propres à Smalltalk, en généralisant la méthode *reverse* à tous les nombres entiers. Recopions, pour cela, cette méthode définie dans la classe *SmallInteger* et introduisons-la dans la classe *Integer*<fnref n="6" />.

<footnote n="5">5 Selon les classes, cette représentation est plus ou moins détaillée. Par défaut, quand une classe CCC n'a pas prévu d'implémentation pour printString, Smalltalk utilise un message printString qui renvoie la chaîne 'a CCC' qui indique, avec l'article indéfini Anglais, que l'objet est une instance de CCC.</footnote>

<footnote n="6">6 Nous verrons, en 1.8, comment modifier, ajouter et supprimer des méthodes pour une classe donnée.</footnote>
On constate maintenant que :

- l'évaluation de `987654 reverse` donne `456789`;

- l'évaluation de `-123456 reverse` donne `-654321`.

et, même si nous supprimons la méthode `reverse` de la classe `SmallInteger`, l'évaluation de `1234 reverse` donnera `4321`.

Nous avons utilisé le fait que la classe `Integer` est la classe-mère des deux classes `SmallInteger` et `LargeInteger`. La classe `SmallInteger` rassemble les entiers que l'on peut gérer directement avec les circuits de calcul de la machine. La classe `LargeInteger` rassemble les autres entiers dont la valeur absolue est plus grande et qui nécessitent des fonctions logicielles de calcul<fnref n="7" />. Ces deux sous-classes de la classe `Integer` héritent de l'ensemble des méthodes de leur classe-mère : en particulier, chaque objet de l'une de ces sous-classes reconnaîtra désormais la méthode `reverse`, dont il aura hérité de la classe `Integer`.

Plus généralement, une sous-classe `SC` d'une classe `C` est considérée comme une spécialisation de `C` qui peut comporter des propriétés supplémentaires par rapport à celles de `C`, ou encore des modifications de certaines propriétés de `C`. Mais, a priori, les objets de `SC` ont un comportement que l'on peut dériver de celui des objets de `C`: on dira qu'ils héritent des méthodes de `C` (méthodes de classes et méthodes d'instances). Si, toutefois, la spécialisation induite par `SC` nécessite la modification du comportement de `SC` ou d'un objet de `SC` pour la méthode `m`, on redéfinira, avec le même sélecteur, la méthode `m`, pour la classe `SC`, conformément au traitement nécessaire. Après cette redéfinition, chaque objet de `SC` reconnaîtra toutes les méthodes de la classe `C`, sauf la méthode `m` pour laquelle il utilisera la redéfinition qui lui est propre.

Chaque sous-classe d'une classe `C` peut elle même être la classe-mère d'une ou plusieurs sous-classes. L'ensemble des classes définies en Smalltalk forme un arbre dont la racine est la classe `Object`. Nous reviendrons sur la structure de cet arbre au paragraphe 1.7 et dans les chapitres suivants. Pour l'instant, intéressons-nous au sous-arbre dont l'origine est la classe `Magnitude` et qui est représenté à la figure 1.8.

La classe `Magnitude` est une classe *abstraite* qui rassemble des méthodes pour comparer, compter et mesurer des valeurs qui seront des objets de ses classes descendantes. Toutes ces classes descendantes hériteront des méthodes de la classe *Magnitude*.

Parmi ces classes descendantes, on trouve :

- la classe *Character*, qui rassemble les caractères du code utilisé,

- la classe *Date*, dont chaque instance représente une date du calendrier,

- les classes *Float*, *Fraction*, *Integer* et *LargeInteger* dont les instances représentent les valeurs numériques que l'on peut utiliser pour des calculs,

- la classe *Time*, dont chaque instance représente une heure de la journée.

<footnote n="7">7 Ces fonctions sont fournies et gérées par Smalltalk, qui permet ainsi de travailler avec des entiers dont la représentation nécessite plus de 32 bits.</footnote>
<figure><img src="image_6.png" /><figcaption>Figure 1.8 : La classe Magnitude et sa descendance</figcaption></figure>

En écrivant précédemment que la classe *Magnitude* est une classe abstraite, nous indiquons que cette classe n'a en principe aucune instance : seules celles de ses sous-classes qui ne sont pas des classes abstraites pourront être instanciées. Une classe abstraite a pour rôle de « mettre en facteur » les traitements génériques qui sont les mêmes pour toutes ses sous-classes. Elle n'est pas instanciable parce qu'elle n'a pas suffisamment de propriétés pour que l'on puisse caractériser avec suffisamment de précision les objets qui relèveraient de cette classe. Ainsi, la classe *Number* est abstraite, parce qu'on ne pourrait décider de la représentation d'un objet *Number* (virgule fixe, virgule flottante, couple *numérateur et dénominateur* ? En revanche, elle définit, pour toutes ses sous-classes, la méthode *squared* :

squared

"Answer the receiver, a type of Number, multiplied by itself."

^ self * self

Nous allons maintenant, dans le paragraphe suivant, étudier certains aspects du mécanisme de l'héritage en nous appuyant sur des exemples de la classe *Magnitude* et de ses sous-classes.

## 1.7 - Héritage et polymorphisme

Examinons la méthode `max:` de la classe *Magnitude*, telle qu'elle est présentée à la figure 1.9. Pour des objets sur lesquels une relation d'ordre est définie, cette méthode permet d'obtenir la plus grande valeur des deux objets `x` et `y`, à partir de l'évaluation de l'expression `x max: y` (l'objet `x` reçoit le message `max: y`).
max: aMagnitude

"Answer the receiver if it is greater than aMagnitude, else answer aMagnitude."

self > aMagnitude

ifTrue: [^self]

ifFalse: [^aMagnitude]

<caption>Figure 1.9 : la méthode max: de la classe Magnitude</caption>

Cette méthode est représentée par un sélecteur à mot-clé et, à ce titre, elle correspond à un message qui sera envoyé avec un argument, cet argument apparaissant immédiatement après le mot-clé. Par exemple, l'expression 3.5 max: 5 représente le message max: qui est envoyé avec l'argument 5 à l'objet 3.5. Dans la définition de la méthode donnée à la figure 1.9, l'argument du message est représenté par aMagnitude.

L'exécution de la méthode compare d'abord l'objet-récepteur à l'argument, en évaluant le prédicat : self > aMagnitude. Cette comparaison renvoie l'objet true ou l'objet false. Si le prédicat est vrai, le bloc<fnref n="8" /> [ ^ self ] est exécuté et la méthode renvoie l'objet récepteur qui est effectivement supérieur à l'argument du message. Sinon, la méthode renvoie l'argument aMagnitude. Le mécanisme de choix représenté par l'envoi du message à mots-clés ifTrue:ifFalse: à un objet de la classe True ou de la classe False sera étudié au chapitre 2.

La méthode max: est héritée par toutes les sous-classes de la classe Magnitude, permettant ainsi à des objets aussi différents l'un de l'autre qu'une date et une fraction de réagir de manière comparable à la même forme de message. Nous mettons ici en évidence le polymorphisme de la méthode max: . Le polymorphisme d'une méthode est la caractéristique qu'a cette méthode de provoquer des réactions différentes en fonction de la classe des objets auxquels elle s'applique. En 1.3, le polymorphisme de reverse est obtenu par la définition, pour chacune des deux classes String et Integer qui accepteront le message, d'un protocole<fnref n="9" /> différent. Il est important de noter qu'avec la programmation orientée objets, le comportement d'un objet O est de la « responsabilité » de O : c'est dans la classe de O que l'on définira le comportement des objets tels que O , alors qu'en programmation classique, ce sont les procédures et les fonctions qui spécifient le traitement.

Cette encapsulation du traitement à l'intérieur de l'objet est une garantie pour le programmeur : tout objet recevant un message, pour lequel il ne dispose pas de méthode de traitement, réagira immédiatement avec une indication d'erreur. En revanche, dans un langage comme C, l'exécution d'une fonction, à laquelle on a fourni un paramètre incorrect, laissera le programme se dérouler jusqu'à ce que les conséquences de l'erreur initiale entraînent une autre anomalie qui, elle, sera bloquante mais n'aura souvent que très peu de rapports avec le paramètre incorrect et sera donc difficile à expliquer.

Si nous revenons à la méthode max : , le polymorphisme est très large puisqu'il s'applique aux dix sous-classes de Magnitude et, à la différence de reverse , il est

<footnote n="8">⁸ Un bloc est une expression ou une suite d'expressions encadrée par des crochets (cf 2.10).</footnote>

<footnote n="9">⁹ On appelle protocole la description d'une méthode, exprimée dans le langage Smalltalk.</footnote>
obtenu par l'effet de l'héritage, mettant ainsi en évidence un des intérêts de ce concept. En Pascal, pour obtenir le même résultat, il aurait fallu définir et décrire autant de fonctions `max(x, y:τ)`, qu'il y a de types `τ` susceptibles de permettre le classement de deux valeurs du type.

Remarquons également que le polymorphisme correspond bien à une caractéristique du raisonnement humain qui est souvent conduit à procéder par analogie pour appliquer un même concept à des situations différentes. Ainsi dirons-nous par exemple que le *10 mars 1989* est entre le *2 mars 1989* et le *30 juin 1990*, que la lettre *d* est entre les lettres *a* et *f*. De la même manière, la méthode *between:and:*, de la classe *Magnitude* (cf figure 1.10), pourra être utilisée avec la classe *Date* ou la classe *Character*, sous-classes de *Magnitude*.

between: min and: max

"Answer true if the receiver is greater than or equal to min and less than or equal to max, else answer false."

^ (min <= self) and: [self <= max]

<caption>Figure 1.10 : la méthode *between:and:* de la classe *Magnitude*</caption>

Notons enfin que le polymorphisme ne s'obtient pas avec un coup de baguette magique. Pour la méthode `max`: de la figure 1.9, il repose sur la réalisation correcte, pour chaque sous-classe de *Magnitude*, de la méthode correspondant au sélecteur > qui, elle aussi, témoignera de son polymorphisme mais en nous contraignant à la redéfinir pour bon nombre de ces sous-classes. Tout héritage de la méthode >, à partir de la classe *Magnitude* n'aurait en effet pas grande signification. Et Smalltalk le prévoit d'ailleurs puisqu'il décrit la méthode > de la classe *Magnitude* de la manière suivante :

> aMagnitude

"Answer true if the receiver is greater than aMagnitude, else answer false."

^ self implementsBySubclass

Si une sous-classe de *Magnitude* ne redéfinit pas cette méthode, l'envoi à un objet de cette sous-classe, d'un message correspondant au sélecteur > provoquera l'exécution de la méthode héritée, c'est-à-dire l'envoi, à ce même objet (*self*), du message *implementedBySubclass*. Ce message correspond à une méthode de la classe *Object* (donc héritée par tous les objets). Il provoque l'apparition d'une fenêtre de diagnostic indiquant que l'objet-récepteur aurait dû redéfinir la méthode.

L'exemple que nous venons de traiter illustre deux caractéristiques fondamentales du polymorphisme :

- le polymorphisme est un mécanisme *naturel* de la programmation orientée objets : chaque fois que pour toutes les classes descendantes d'une classe C, une méthode *m* peut être décrite par le même énoncé, le fait de définir *m* dans la classe C assure le polymorphisme. Ainsi, quelle que soit la sous-classe C' de *Magnitude* à laquelle appartiennent
les objets x et y le principe de l'évaluation de $x \max: y$ peut être exprimé par l'énoncé :

> le plus grand de deux objets x et y est x, si x est supérieur à y, sinon c'est y.

- Ce mécanisme naturel doit être construit. Si, pour plusieurs classes $C_1, C_2, ..., C_n$, une méthode $m$ s'exprime par le même énoncé, dans lequel on fait appel à une méthode $m'$ et si $m'$ ne peut être exprimée par un énoncé unique pour toutes les classes $C_1, C_2, ..., C_n$, il faut construire le polymorphisme de $m'$ pour assurer celui de $m$. C'est-à-dire qu'il faut rédiger, pour $m'$, autant de protocoles différents qu'il y a d'énoncés distincts pour exprimer cette méthode.

## 1.8 - Les outils de Smalltalk pour la manipulation des classes

Après cette première approche de la programmation orientée objets, le lecteur qui désire se familiariser avec l'utilisation de Smalltalk peut, s'il dispose d'un poste de travail sur lequel le logiciel correspondant est disponible, effectuer ses premières manipulations. Pour lui faciliter le travail, nous indiquons, dans ce paragraphe, comment il est possible d'explorer et de manipuler l'arborescence des classes.

Smalltalk fournit, pour cela, une interface permettant de modifier une méthode d'une classe donnée, d'ajouter une méthode à une classe donnée, de créer une nouvelle classe en la définissant comme sous-classe d'une classe donnée. Pour ce faire, il est nécessaire d'ouvrir à l'écran une fenêtre d'édition de la hiérarchie des classes (class hierarchy browser). Pour faire apparaître une telle fenêtre, on sélectionnera, dans le menu général de Smalltalk, l'option browse classes<fnref n="10" />.

Selon les implémentations, la présentation d'une fenêtre d'édition de la hiérarchie des classes prendra diverses formes mais on retrouvera toujours les mêmes fonctionnalités pour la consultation et la mise à jour de l'ensemble des classes. Nous présentons ici, aux figures 1.11 et 1.12, deux états successifs d'une telle fenêtre avec IBM Smalltalk.

La fenêtre de la figure 1.11 comprend, sous sa barre de titre, plusieurs panneaux. Le panneau supérieur gauche est le panneau des classes. Il donne une vue partielle de l'arbre des classes. En cliquant, avec le bouton gauche, sur le nom d'une classe, on sélectionne cette classe, faisant ainsi apparaître, dans les autres panneaux, certaines caractéristiques de cette classe. La figure 1.11 montre l'état de la fenêtre d'édition des classes quand on a fait défiler l'arbre jusqu'à la classe Magnitude, puis sélectionné cette classe.

Quand une classe vient d'être sélectionnée, le panneau inférieur de la fenêtre donne la définition de la classe, en énumérant notamment, s'il y a lieu, ses variables d'instances (cf. 2.6). Dans l'exemple de la figure 1.11, la définition de la classe Magnitude indique que cette classe est une classe-fille de la classe Object et qu'elle n'a pas de variables d'instances. Le panneau inférieur de la fenêtre est le

<footnote n="10">10 Les moyens de faire apparaître ce menu général dépendent des implémentations de Smalltalk.</footnote>
panneau d'édition. Ce panneau est géré par un objet éditeur de texte qui permettra
à l'utilisateur de modifier les descriptions de classes ou les méthodes qui y seront
affichées.

<figure><img src="image_7.png" /><figcaption>Figure 1.11 : la fenêtre d'édition des classes avec sélection de la classe *Magnitude*</figcaption></figure>

Dans le panneau supérieur droit de la fenêtre, est affichée la liste des sélecteurs des méthodes d'instance de la classe Magnitude. On peut faire défiler cette liste et sélectionner l'une de ces méthodes, en cliquant sur son sélecteur. On peut aussi faire apparaître la liste de méthodes de classes en cliquant sur la barre de libellé instance qui sera alors remplacé par le libellé class.

Quand un sélecteur est sélectionné dans le panneau des sélecteurs, le protocole
de la méthode correspondante apparaît dans le panneau d'édition. La figure 1.12
montre l'état de la fenêtre d'édition des classes ouverte sur la descendance de
*Magnitude*, dans laquelle on a sélectionné la classe *Number* et, pour cette classe,
la méthode *even*. Quand une méthode est affichée dans le panneau d'édition,
l'utilisateur peut alors examiner le texte de la méthode et, éventuellement le
modifier. Pour valider les modifications, l'utilisateur fera apparaître le menu du
panneau d'édition, en cliquant brièvement sur le bouton droit de la souris, le
curseur-souris étant placé dans le panneau d'édition. Il sélectionnera ensuite
l'option *save* de ce menu.

D'une manière générale, cliquer brièvement avec le bouton droit de la souris, fait apparaître, dans le panneau actif<fnref n="11" />, le menu de ce panneau (s'il en existe un). Le panneau des classes dispose lui aussi d'un menu que l'on peut faire apparaître de la même manière. Une option du menu du panneau des classes, l'option *add subclass*, permet d'ajouter une sous-classe à la classe sélectionnée. L'activation de

<footnote n="11">11 Le panneau actif est celui dans lequel est placé le curseur de la souris.</footnote>
cette option déclenche un dialogue dans lequel Smalltalk demande à l'utilisateur d'indiquer les caractéristiques de la nouvelle classe (cf. chapitre 3).

<figure><img src="image_8.png" /><figcaption>Figure 1.12 : une vue partielle sur la descendance de la classe *Magnitude*</figcaption></figure>

Pour terminer, décrivons brièvement les options du menu du panneau des sélecteurs qui comprend quatre options : *delete methods*, *new method template*, *browse implementors* et *browse senders*.

**delete methods**

cette option permet de supprimer la méthode sélectionnée (pour la classe considérée, cette méthode disparaît de l'environnement de Smalltalk).

**new method template**

quand on active cette option, le panneau d'édition devient actif pour la saisie d'une nouvelle méthode (que l'on ajoutera à l'environnement de Smalltalk en utilisant l'option *save* du menu du panneau d'édition).

**browse implementors**

quand un sélecteur est sélectionné dans le panneau des sélecteurs, l'activation de cette option ouvre une nouvelle fenêtre qui donnera la liste des classes qui ont défini une méthode correspondant à ce même sélecteur. Cette option permet donc d'avoir une vision globale du polymorphisme d'une méthode.

**browse senders**

quand un sélecteur est sélectionné dans le panneau des sélecteurs, l'activation de cette option ouvre une nouvelle fenêtre qui indique les
méthodes qui font appel à la méthode indiquée par le sélecteur. Cette fenêtre indique les classes concernées et, pour chaque classe, les sélecteurs des méthodes qui font appel à la méthode sélectionnée. Cette option est donc utile, chaque fois que l'on souhaite modifier une méthode. En l'utilisant, on obtient la liste des autres méthodes (et des classes correspondantes) qui peuvent être touchées par la modification.

## Avis au lecteur

Dans la suite de cet ouvrage, nous allons étudier en détail les caractéristiques de la programmation objets avec Smalltalk. Pour les chapitres 2 à 5, nous présenterons des exemples réalisés avec l'implémentation de Digitalk, Smalltalk V. Pour tous les thèmes abordés par ces chapitres, les trois versions les plus courantes de Smalltalk sont très proches et le lecteur n'aura aucune difficulté pour passer de Smalltalk V à Smalltalk 80 ou IBM Smalltalk. Dans le chapitre 6, nous étudions la programmation d'une application sous interface graphique, pour laquelle les différences entre les trois implémentations sont importantes. Pour ce dernier chapitre, nous avons choisi l'interface graphique MS-Windows et l'implémentation IBM Smalltalk.

Les quelques exemples que nous venons de présenter dans ce chapitre ont été composés avec la version 2 de IBM Smalltalk et, pour la site du livre, nous signalons dans le tableau suivant les quelques différences avec Smalltalk V, pour les notations utilisées dans le chapitre et reprises dans les chapitres suivants.

<table><thead><tr><td>IBM Smalltalk</td><td>Smalltalk V</td></tr></thead><tbody><tr><td>méthode reverse</td><td>méthode reversed</td></tr><tr><td>méthode subStrings</td><td>méthode asiansubStrings</td></tr><tr><td>option Execute</td><td>option do it</td></tr><tr><td>option Display</td><td>option show it</td></tr></tbody></table>
## 2 - Le langage Smalltalk

Nous allons maintenant préciser sur quel formalisme s'appuient les exemples que nous avons donnés dans le premier chapitre de cet ouvrage.

### 2.1 - Les expressions

Jusqu'à présent, nous avons manipulé des expressions sans indiquer quelles étaient les règles qui permettaient de former ces expressions. En Smalltalk, une expression peut-être :

- une constante littérale comme `123` ou `'Durand'`,

- un nom de variable comme `x` ou `chaine`,

- un message comme `chaine reversed` ou `x between: 5 and: y`,

- un bloc rassemblant, entre crochets, des déclarations facultatives de variables de blocs suivies d'une séquence d'expressions séparées les unes des autres par des points :

```
[:lettre|
lettre asUppercase printOn: Transcript.
Transcript cr]
```

Il importe de noter qu'en Smalltalk, tout est objet : ainsi, une expression représente toujours un objet qui est le résultat de l'évaluation de cette même expression. Décrivons maintenant de manière précise les formes d'expressions que nous venons de citer.

### 2.2 - Les constantes littérales

Ce sont des nombres (instances d'une sous-classe de *Number*), des chaînes (instance de la classe *String*), des caractères (instances de la classe *Character*), des symboles (instances de la classe *Symbol*) ou des tableaux (instances de la classe *Array*) représentés directement par leur valeur.
### 2.2.1 - Les constantes numériques

Une constante numérique peut être un objet de la classe *Integer*, *Float* ou *Fraction* :

`32 -120 1.414 15e-5 3/4`

La classe *Fraction* (cf 2.6) représente les nombres rationnels non entiers et, à chaque instance de cette classe, est associé un couple de deux valeurs entières : numérateur et dénominateur. Une instance de cette classe est créée par l'envoi, à un entier *e1*, du message `/e2` (à condition que l'entier *e2* ne soit pas un diviseur de *e1*). Une constante de la classe *Fraction* est donc représentée par deux constantes entières, séparées par le sélecteur `/`.

### 2.2.2 - Les constantes-chaînes

Une constante-chaîne est une suite de caractères encadrée par deux apostrophes. Si l'un des caractères est une apostrophe, celle-ci doit être redoublée pour ne pas être confondue avec l'apostrophe de fin de chaîne. Une constante-chaîne est une instance de la classe *String*.

### 2.2.3 - Les constantes-caractères

Une constante-caractère est une instance de la classe *Character* que l'on représente par le caractère `$` suivi par le caractère :

`$a` représente le a minuscule

`$!` représente un point d'exclamation

`$$` représente le caractère $

`$` représente l'espace ($ suivi d'un blanc).

On notera que l'utilisation du symbole `$` sert à bien distinguer une instance de la classe *Character* d'une instance de la classe *String* ne comportant qu'un seul caractère. Ainsi, `$a` et 'a' sont deux objets différents.

### 2.2.4 - Symboles et constantes-symboles

Un symbole est un objet de la classe *Symbol*, elle-même sous-classe de la classe *String*. La principale différence entre un symbole et une chaîne, instance de la classe *String*, est que le symbole est invariable alors que les caractères d'une chaîne peuvent être modifiés. Ainsi, la séquence

`|chaîne| chaîne := 'abcde'.
chaîne at: 2 put: $Z.`

modifie la première valeur de l'objet *chaîne* et se termine avec, pour cet objet, la valeur 'aZcde'. A l'inverse, un symbole est une chaîne invariable de caractères qui représentera la plupart du temps un identificateur (nom de classe, sélecteur de méthode, etc...). Il s'écrira sans être encadré par des apostrophes (ce qui permet de distinguer un symbole d'une instance de la classe *String*).
Quand un symbole est présent dans une expression, Smalltalk considère qu'il représente l'objet qu'il désigne. Par exemple, l'expression *String new* est formée de deux symboles :

*String* qui est le nom de la classe *String*,

*new* qui est le sélecteur de la méthode *new*.

L'évaluation de cette expression amènera Smalltalk à envoyer à la classe *String* le message *new* (et donc à créer une nouvelle instance de cette classe).

Si l'on souhaite que Smalltalk traite un symbole comme une instance de la classe *Symbol* (et non comme l'objet que ce symbole représente), il faut exprimer ce symbole sous la forme d'une *constante-symbole* qui s'écrira avec le caractère # précédant immédiatement le premier caractère du symbole. Pour illustrer la différence de traitement, considérons la séquence d'expressions suivante :

|s1 s2 chaine|

s1 := String.

s2 := #String.

chaine := s1 new

Après l'évaluation de la deuxième expression les variables *s1* et *s2* représentent deux objets distincts : *s1* désigne la classe *String* et *s2* désigne le symbole *String*. En fin d'évaluation la variable *chaine* représente une chaîne vide (résultat de l'envoi du message *new* à la classe *String*).

### 2.2.5 - Les tableaux

Un tableau est un objet de la classe *Array* (cf chapitre 3). Une *constante-tableau* s'écrit en énumérant les éléments du tableau et en encadrant l'énumération par un couple de parenthèses, la parenthèse gauche étant précédée par le caractère #. Voici quelques exemples de constantes-tableau :

## `(10 -3 28)` tableau de trois éléments dont les valeurs sont 10, -3 et 28.

## `#('Irène' 'Durand' 1945 'Paris')`
tableau de quatre éléments dont les trois valeurs de rang 1, 2 et 4 sont des chaînes et la valeur de rang 3 est l'entier 1945.

## `(22 ($A $B $C) 3 57)`
tableau de quatre éléments, dont le second est lui-même un tableau de trois caractères.

A propos du dernier exemple, on notera que la syntaxe de Smalltalk impose, lorsqu'un élément d'une constante-tableau est lui-même une constante-tableau, d'omettre le caractère #. En effet, la syntaxe d'une constante-tableau est définie par les règles de la page suivante<fnref n="1" />.

<footnote n="1">1. cf [Dig 86] pour les conventions de la présentation syntaxique (Smalltalk syntax, page 454).</footnote>
```html
<constanteTableau> ::= "#" <tableau>
<tableau> ::= "(" {<constante numérique>
| <constante-chaîne>
| <symbole>
| <tableau>
| <constante-caractère>
} ")"
```

## 2.3 - Les séquences d'expressions

Une séquence d'expressions (cf 1.2) est une suite d'expressions séparées les unes des autres par des points. La dernière expression de la séquence n'est donc pas suivie d'un point. Quand une séquence d'expressions représente la description d'une méthode, il est fréquent que la dernière expression soit précédée par le signe `^` qui représente la fin d'exécution de la méthode avec le renvoi de la valeur de l'expression qui suit le `^`.

Plus généralement, le signe de renvoi `^` peut apparaître devant n'importe quelle expression d'une séquence. Si cette expression est évaluée, sa valeur est renvoyée en réponse et l'évaluation de la séquence est terminée. Examinons, par exemple, la figure 2.1, qui donne une réalisation possible de la méthode *quo*: de la classe *Integer*<fnref n="2" />. Si *entier* est un objet de cette classe et si *nombre* est une valeur numérique, l'évaluation de l'expression

`entier quo: nombre`

renvoie la partie entière du quotient de *entier* par *nombre*.

```html
quo: aNumber
"Answer the integer quotient of the receiver divided by aNumber
with truncation toward zero."
aNumber = 0 ifTrue: [^self zeroDivisor].
^(self / aNumber) truncated
```

<caption>Figure 2.1 : une réalisation de la méthode *quo*: pour la classe *Integer*</caption>

La séquence de la figure est une série de deux expressions. La première expression est une évaluation conditionnelle : si l'argument *aNumber* de la méthode est nul, le bloc qui suit le sélecteur *ifTrue*: est exécuté. Dans ce cas, le message *zeroDivisor* est envoyé à l'objet-récepteur et la réponse obtenue constituera la réponse de la méthode : l'exécution est alors terminée avant l'évaluation de la dernière expression. Le traitement du message *zeroDivisor* provoquera l'apparition d'une fenêtre de diagnostic qui indiquera l'erreur rencontrée.

Si *aNumber* n'est pas nul, le bloc qui suit *ifTrue*: n'est pas exécuté et l'évaluation de la dernière expression renvoie le résultat attendu.

<footnote n="2">2 La représentation donnée à la figure ne correspond pas exactement à la réalisation en Smalltalk V qui utilise, pour cette méthode, une primitive en langage-machine.</footnote>
## 2.4. - Les règles de priorité pour l'évaluation d'une expression

Nous avons vu, au paragraphe 2.1 qu'une expression pouvait être un message. Nous avons, en 1.1, distingué plusieurs catégories de messages. Rappelons-les brièvement.

### 2.4.1 - Messages unaires

Un message unaire est un message sans argument, qui se représente par le sélecteur de la méthode correspondante :

```
'Alain' size "la chaîne Alain reçoit le message unaire size"
x reversed "l'objet représenté par la variable x reçoit le message unaire reversed"
```

### 2.4.2 - Messages binaires

Un message binaire comprend un et un seul argument qui suit immédiatement le sélecteur :

```
5 + 3 "l'objet 5 reçoit le message binaire + 3 (la réponse est l'objet 8)"
18 @ 24 "l'objet 18 reçoit le message binaire @ 24 et renvoie le point de coordonnées 18 et 24"
```

```
#(2 3) , #('a' 'b')
"l'objet #(2 3) reçoit le message , #('a' 'b') (sélecteur « virgule ») et renvoie le tableau #(2 3 'a' 'b')"
```

On notera que le dernier message binaire des exemples précédents correspond au sélecteur, (virgule)<fnref n="3" /> qui représente la concatenation de la valeur du récepteur avec celle de l'argument.

### 2.4.3 - Messages à mot(s)-clé(s)

Un message à mot(s)-clé(s) est un message qui comprend un ou plusieurs arguments. Chaque argument est précédé d'un mot-clé qui se termine obligatoirement par le signe : (deux points). Le sélecteur du message est la concatenation des mots-clés :

```
5 max: 10 "sélecteur max:"
x between: y and: z "sélecteur between:and"
```

<footnote n="3">3 Ce sélecteur correspond à une méthode d'instance définie pour les classes IndexedCollection et SortedCollection (cf chapitre 4) et pour toutes les sous-classes de ces classes. L'évaluation de l'expression a,b où a et b sont des objets de la classe C renvoie un nouvel objet de C, formé de la valeur de a, suivie de celle de b.</footnote>
### 2.4.4 - Les priorités

Quand une expression comprend plusieurs messages différents, Smalltalk applique les règles de priorité suivantes :

- Les messages unaires ont priorité sur les messages binaires, qui ont eux-mêmes priorité sur les messages à mots-clés.

- Pour un même niveau de priorité, l'évaluation se fait de gauche à droite.

- L'utilisation de parenthèses permet de modifier les priorités : les messages entre parenthèses sont évalués d'abord.

La figure 2.2 présente quelques exemples qui mettent en relief ces règles.

<table><caption>Figure 2.2 : évaluation et priorités</caption><tr><td>1 + 2 * 3</td><td>"renvoie 9"</td></tr><tr><td>1 + (2 * 3)</td><td>"renvoie 7"</td></tr><tr><td>3 + 'abcdef' size</td><td>"évaluation de 'abcdef' size qui renvoie 6, puis de 3 + 6 qui renvoie 9"</td></tr></table>

A propos des opérations arithmétiques, on notera, comme le montre le premier des trois exemples précédents, que les règles de priorité de Smalltalk ne sont pas celles des langages de programmation classiques pour les mêmes opérations. Il appartiendra donc au programmeur de veiller, en Smalltalk, à l'utilisation de parenthèses pour faire exécuter, en priorité, un calcul figurant, dans une expression, à droite d'un autre calcul moins prioritaire.

Egalement à titre d'exemple, notons que, dans l'expression finale, présentée à la figure 2.1 :

```
^(self / aNumber) truncated
```

l'utilisation des parenthèses est obligatoire pour déclencher l'évaluation du message binaire / avant celle du message unaire *truncated*.

Quand un même objet doit recevoir successivement plusieurs messages, on peut écrire une « *cascade* » de messages dans laquelle chaque message est séparé du suivant par un point-virgule :

```
Transcript nextPutAll: 'Alain';
        cr;
        nextPutAll: 'Durand';
        positionAtBeginning
```

Dans l'exemple précédent, l'objet *Transcript* reçoit successivement quatre messages. Le premier affiche le prénom 'Alain', le second envoie le curseur en début de ligne suivante, le troisième affiche le nom 'Durand' et le dernier place le curseur au début du texte contenu dans *Transcript*.
## 2.5. - Les variables

Une variable Smalltalk représente un objet. De manière interne, elle contient un pointeur sur cet objet, sauf si l'objet peut être représenté par un octet ou un mot-machine : dans ce cas, l'objet est stocké directement dans la variable. Quand une variable est définie, mais qu'elle n'a pas encore reçu de valeur, elle a la valeur *nil*. La valeur *nil* est elle-même un objet<fnref n="4" /> qui est la seule instance de la classe *UndefinedObject*. On peut vérifier cette affirmation en évaluant, avec *show it*<fnref n="5" />, l'expression

`|x| x`

qui rend la valeur *nil*. Deux méthodes de la classe *Object* permettent de savoir si une variable a déjà reçu une valeur. Ce sont les méthodes *isNil* et *notNil*.

Il existe, en Smalltalk, trois catégories de variables : les variables d'instance, les variables temporaires et les variables partagées. La syntaxe impose d'attribuer aux variables d'instance et aux variables temporaires des noms commençant par une lettre minuscule et aux variables partagées des noms commençant par une majuscule. Nous allons, dans les paragraphes suivants, étudier les trois catégories de variables.

## 2.6 - Les variables d'instance

Les variables d'instance sont des variables associées à un objet et leur durée de vie est celle de cet objet. Elles représentent la connaissance statique privée de cet objet (les méthodes d'instance représentant, elles, la connaissance dynamique). Examinons par exemple la figure 2.3, qui reproduit la définition de la classe *Fraction*.

```number subclass: #Fraction
instanceVariableNames: 'numerator denominator '
classVariableNames: ''
poolDictionaries: ''
```

<caption>Figure 2.3 : La classe *Fraction*</caption>

Cette définition indique que la classe dont le nom est le symbole `#Fraction` est une sous-classe de la classe *Number* et que chaque objet de cette classe possède

<footnote n="4">4. Ce qui permet d'affirmer la généralité de la première phrase du paragraphe 2.5. Même quand elle n'a pas encore reçu de valeur, une variable désigne un objet.</footnote>

<footnote n="5">5. Nous avons vu au chapitre 1 (cf 1.3) que l'on pouvait, dans une fenêtre d'édition de texte, sélectionner une séquence d'expressions puis déclencher l'évaluation de la séquence sélectionnée avec l'option do it ou l'option show it. Ici, pour l'évaluation de `|x| x`, il faut utiliser `show it`. Une évaluation avec do it aurait bien rendu la valeur *nil* mais ne l'aurait pas affichée dans la fenêtre.</footnote>
deux variables d'instance respectivement appelées *numerator* et *denominator*. Si un objet de cette classe représente le rationnel *n/d*, la variable d'instance *numerator* aura la valeur *n* et la variable d'instance *denominator* aura la valeur *d*. On peut créer un objet de classe *Fraction* soit avec la méthode de classe *new* héritée de la classe-mère, soit avec la méthode de classe *numerator:denominator*: qui donne une valeur déterminée aux variables d'instance, comme le montre la séquence de la figure 2.4.

```
|f1 f2|
f1 := Fraction new.
    "les variables d'instance numerator et denominator de la fraction f1 ont la valeur nil"
f2 := Fraction numerator: 3 denominator: 5.
    "les variables d'instance de f2 ont les valeurs 3 et 5"
f2 := f2 * (2/7)
    "f2 prend la valeur 61/35"
```

<caption>Figure 2.4 : quelques instanciations de la classe *Fraction*</caption>

Il est important de noter que chaque objet de la classe *Fraction* possède ses propres variables d'instances qui ne sont accessibles que par les messages qui lui sont adressés. Les variables d'instance d'un objet *x* ne peuvent être modifiées qu'avec l'accord de ce même objet : seule une méthode propre à *x* peut changer la valeur d'une variable d'instance de *x*.

Pour illustrer cette caractéristique de la programmation orientée « objets », étudions l'évaluation de la dernière expression de la figure 2.4 :

```
f2 := f2 * (2/7)
```

Au moment de l'évaluation de cette expression, Smalltalk manipule deux variables d'instance *numerator* (celle de *f2* et celle de *2/7*) et deux variables d'instance *denominator*. Pour bien comprendre comment sont traitées ces variables, examinons les méthodes de la figure 2.5. Trois méthodes d'instance de la classe *Fraction* y sont décrites. Les deux premières permettent d'accéder aux valeurs des variables d'instance d'un objet de cette classe. En effet, une des caractéristiques de la programmation orientée « objets » est de donner aux objets la responsabilité de la gestion des informations (données et méthodes) dont ils sont propriétaires et ceci dans le but d'une plus grande sécurité en programmation. Ainsi, la plupart des opérations incorrectes sont immédiatement signalées et diagnostiquées par les objets qui possèdent les informations mises en danger par ces opérations incorrectes<fnref n="6" />. Dans cet esprit, pour accéder à la connaissance privée d'une fraction (c'est-à-dire à son numérateur et à son dénominateur), il est nécessaire d'envoyer des messages à cette fraction. Pour la commodité de la compréhension, ces messages portent ici le même nom que les variables correspondantes.

<footnote n="6">6 A la différence de la programmation classique, avec laquelle une procédure ou une fonction peut, par erreur, endommager une donnée sans que le traitement soit interrompu.</footnote>
**numerator**

"Answer the numerator of the receiver."

^numerator

**denominator**

"Answer the denominator of the receiver."

^denominator

**\* aNumber**

"Answer the result of multiplying the receiver by aNumber."

^ (numerator * aNumber numerator)

/

(denominator * aNumber denominator)

<caption>Figure 2.5 : quelques méthodes d'instances de la classe Fraction</caption>

Dans la figure 2.5, la réalisation de la méthode \*, qui reçoit l'argument *aNumber*, illustre ce que nous venons de dire. Si, dans l'expression *x*\*\*\*y, *x* représente une fraction, lorsque l'expression est évaluée, la méthode \* de la classe *Fraction* est exécutée. Alors, seule la variable *numerator* de l'objet-récepteur *x* est directement accessible. La valeur de la même variable d'instance de *y* devra être obtenue par le message *numerator* envoyé à *y*. Ainsi, la première partie du calcul effectué par la méthode \*, pour multiplier les numérateurs de *x* et *y*, s'écrit-elle :

(numerator * aNumber numerator)

Remarquons enfin que cette manière de procéder assure le polymorphisme de la méthode \* de la classe *Fraction*. En effet, la responsabilité de fournir son numérateur et son dénominateur incombant à chaque objet, si l'argument *aNumber* n'est pas une fraction mais un objet de la classe *Float* ou d'une sous-classe de la classe *Integer*, cet argument renverra les valeurs correctes grâce aux méthodes héritées de la classe-mère *Number*, que nous présentons à la figure 2.6.

**numerator**

"Answer the numerator of the receiver."

Default is the receiver which can be overridden by the subclasses."

^self

**denominator**

"Answer the denominator of the receiver."

Default is one which can be overridden by the subclasses."

^1

<caption>Figure 2.6 : les méthodes numerator et denominator de la classe Number</caption>

Dans les exemples que nous venons d'étudier, les variables d'instances *numerator* et *denominator* sont des variables d'instances *nommées*, c'est-à-dire désignées par leur nom (en anglais : *named instance variables*). Un objet peut
aussi avoir des variables d'instances *indexées* (*indexed instance variables*) qui sont repérées par un indice entier et ne sont accessibles qu'à travers un message adressé à l'objet.

Ainsi, un objet de la classe *Array* est un tableau dont les variables d'instances sont indexées et représentent les éléments du tableau. De même, un objet de la classe *String* possède autant de variables d'instance indexées que sa valeur comporte de caractères. Par exemple, l'évaluation de la séquence suivante :

```
|initiale|
initiale := 'Marie' at: 1
```

affectera, à la variable *initiale*, la valeur $M$.

Pour une même classe, le nombre de variables d'instance nommées est le même pour tous les objets de la classe. En revanche, le nombre des variables d'instances peut varier d'un objet à l'autre. Ainsi, pour la classe *Array*, ce nombre est, pour chaque objet, égal au nombre d'éléments du tableau représenté.

Les méthodes *at:* et *at:put:*., qui permettent respectivement d'obtenir et de modifier la valeur d'une variable d'instance indexée sont définies dans la classe *Object* et sont donc héritées par toutes les classes. Dans ces deux méthodes, la valeur de l'argument correspondant au mot-clé *at:* représente le rang (supérieur ou égal à 1) de la variable d'instance que l'on souhaite examiner ou modifier. Si l'objet récepteur n'a pas de variables d'instances ou si le rang indiqué correspond à une variable inexistante, une fenêtre d'erreur apparaît. La séquence suivante illustre le fonctionnement de ces deux méthodes.

```
|x|
x := Array new: 5.
    "x est un tableau de cinq éléments qui ont tous la valeur nil"
x at: 3 put: 1990.
    "l'élément de rang 3 prend la valeur 1990"
x at:1 printOn: Transcript.
    "la valeur nil apparaît sur la fenêtre Transcript"
x at: 8 printOn: Transcript
    "une fenêtre de diagnostic apparaît car x n'a pas de variable d'instance de rang 8"
```

## 2.7 - Gestion dynamique de la mémoire disponible

Pour gérer les objets qu'il est amené à manipuler, le processeur Smalltalk va utiliser un espace-mémoire dans lequel il distinguera des domaines disponibles et des domaines occupés par la représentation des objets utilisés. Chaque nouvel objet créé restreint l'espace disponible au profit de l'espace occupé. Pour comprendre cette gestion de l'espace-mémoire, étudions l'évaluation de la séquence de la figure 2.7.
```
|x| x := Array new: 6.
    x at: 1 put: 'Alain'.
    x at: 5 put: 1945.
    x printOn: Transcript
```

<caption>Figure 2.7 : création d'une instance de la classe Array</caption>

Pour Smalltalk, la première opération de l'évaluation est le traitement de la définition de la variable temporaire $x$ (cf 2.8). Une fraction de l'espace disponible va être réservée pour y implanter la représentation de $x$. La valeur de $x$ après cette implantation est la valeur nil.

En fait, dans l'espace disponible de Smalltalk, le résultat de l'implantation de $x$ est représenté par la figure 2.8. La valeur nil est elle-même un objet, unique instance de la classe UndefinedObject et la variable $x$ a pour valeur interne un pointeur qui indique l'adresse de l'emplacement-mémoire qui contient l'objet nil. Le programmeur, cependant, ne manipulera jamais ce pointeur : pour lui, tout se passe comme si la valeur de $x$ était directement l'objet nil.

<figure><img src="image_9.png" /><figcaption>Figure 2.8 : la variable temporaire $x$ après son implantation en mémoire</figcaption></figure>

Ensuite, l'évaluation de la première affectation : $x := Array new: 10$ s'effectue en deux temps. Smalltalk évalue d'abord le message new: 6 adressé à la classe Array : il prélève, sur l'espace disponible, le domaine nécessaire pour la représentation d'un tableau de six éléments qui auront tous la valeur nil<fnref n="7" />. Smalltalk va ensuite réaliser l'affectation : la valeur de la variable $x$ va être modifiée pour désigner le tableau nouvellement créé. Cette valeur sera celle d'un pointeur qui désignera l'emplacement du domaine contenant la représentation du tableau. Après cette affectation, la variable $x$ représente donc le tableau créé.

Ensuite, l'évaluation de $x at: 1 put: 'Alain' (message at:put: envoyé à l'objet x)$ va conduire Smalltalk à prélever à nouveau, sur l'espace disponible, le domaine nécessaire à la représentation de la chaîne 'Alain'. Après cette opération, Smalltalk changera la valeur du premier élément du tableau désigné par $x$: la nouvelle valeur sera un pointeur qui indiquera l'emplacement de la mémoire où est implantée la représentation de la chaîne 'Alain'.

De même, le traitement du second message at:put: envoyé à l'objet $x$ modifiera la valeur du cinquième élément du tableau. La figure 2.9 donne une représentation des domaines de l'espace-mémoire utilisés après le traitement de ce second message.

Avant d'étudier comment se terminera l'exécution de la séquence présentée à la figure 2.7, revenons sur le terme de « pointeur », que nous avons utilisé à plusieurs reprises dans les explications précédentes. En programmation, un

<footnote n="7">7 C'est-à-dire qui contiendront tous un pointeur vers l'objet nil.</footnote>
pointeur est, d'une manière générale, une entité qui désigne l'emplacement d'une information dans l'espace-mémoire occupé par un programme.

<figure><img src="image_10.png" /><figcaption>Figure 2.9 : exemple d'utilisation de l'espace-mémoire</figcaption></figure>

A partir de l'exposé que nous venons de faire de l'utilisation de pointeurs par Smalltalk, le lecteur déjà familiarisé avec des langages de programmation classiques tels que Pascal ou C pourrait être amené à penser que la pratique de la programmation orientée « objets » nécessite, pour le programmeur, la manipulation de pointeurs. Il n'en est rien : la gestion de l'espace-mémoire est faite par Smalltalk sans que le programmeur ait à s'en soucier<fnref n="8" />. Et, si nous revenons à l'exemple de la figure 2.9, le programmeur n'a pas à se préoccuper de l'utilisation des pointeurs. Pour lui, l'objet *x* est le tableau représenté sur la figure et, par exemple, l'élément de rang 1 de ce tableau a pour valeur la chaîne 'Alain'.

Revenons maintenant à la séquence examinée figure 2.7. La dernière expression évaluée :

```
x printOn: Transcript
```

enverra, dans la fenêtre Transcript, la représentation de l'objet *x*. Cette représentation apparaîtra de la manière suivante :

```
('Alain' nil nil nil nil 1945 nil)
```

Après l'évaluation de cette dernière expression, si l'on suppose que l'évaluation de la séquence de la 2.5 a été déclenchée avec *show it* ou *do it*, le processeur Smalltalk va supprimer la variable temporaire *x* et donc réincorporer à l'espace disponible l'objet que cette variable représentait. Ainsi, le tableau, la constante-chaîne 'Alain', la constante numérique 1945 disparaîtront de l'environnement

<footnote n="8">8. Bien entendu, la manière dont le processeur Smalltalk gèrera la mémoire disponible influera sur la performance des applications. Une gestion efficace impliquera que Smalltalk limite la fragmentation de la mémoire due au fait que les domaines réservés ne sont pas forcément rendus à l'espace disponible selon l'ordre chronologique des réservations (un domaine est rendu à l'espace disponible quand l'objet qu'il représente est détruit). En général, le processeur Smalltalk disposera d'un mécanisme interne de « ramasse-miettes » (en anglais garbage collection) qui permettra de rassembler les fragments disponibles, chaque fois que la grande dispersion de ces fragments pénalisera trop les performances.</footnote>
Smalltalk. Des cinq objets représentés à la figure 15, seul l'objet *nil*, unique objet de la classe *UndefinedObject*, subsistera à la fin de l'évaluation de la séquence de la figure 2.7.

Ainsi, chaque fois qu'un objet est supprimé de l'environnement Smalltalk, toutes ses variables d'instance (qu'elles soient nommées ou indexées) sont également supprimées.

## 2.8 - Les variables temporaires

Nous connaissons déjà ces variables que nous avons souvent utilisées pour l'évaluation d'une expression. Ce sont des variables dont le nom commence toujours par une lettre minuscule et qui disparaissent de l'environnement de Smalltalk dès que la séquence dans laquelle elles ont été déclarées a été évaluée. Ces variables peuvent être classées en trois catégories.

### 2.8.1 Les variables temporaires d'une séquence d'expressions

Ce sont celles qui se déclarent obligatoirement en tête d'une séquence d'expressions, entre une paire de barres verticales ( | et | ). La séquence pour laquelle ces variables sont déclarées et utilisées peut être une séquence d'expressions saisie dans une fenêtre d'édition de texte ou encore le protocole complet d'une méthode. Quand on déclare une variable temporaire $v$, cette variable désigne, au départ, l'objet *nil*. Pour lui faire désigner un autre objet $o$ (i.e. lui « donner une nouvelle valeur »), on doit utiliser une affectation et écrire :

$v := o$

Après l'évaluation de cette affectation, la variable $v$ désigne l'objet $o$ (cf 2.7) et l'on peut, par abus de langage, parler de l'« objet $v$ ». On notera à ce propos, que nous rencontrons là l'unique exception à la règle qui veut que, dans Smalltalk tout soit « objet ». Une variable est un *pointeur* sur un objet et le symbole $:= $ n'est pas le sélecteur d'une méthode. Ainsi, comme un objet ne peut traiter que les messages qui correspondent à une méthode qu'il reconnaît, on ne peut faire une affectation directe à un objet<fnref n="9" />. Par exemple, si $o1$ et $o2$ sont deux objets, l'évaluation de la séquence

$|x| x := o1. x := o2.$

n'aura pas pour effet de « transformer » l'objet $o1$ en l'objet $o2$. Dans cette évaluation, $x$ aura désigné successivement l'objet *nil*, l'objet $o1$ et enfin l'objet $o2$.

### 2.8.2 Les arguments d'un bloc

Un bloc est une séquence d'expressions encadrée par une paire de crochets [ et ]. Un bloc est un objet de la classe *Context* (cf 2.10) et peut comporter des arguments qui sont des variables locales déclarées au début du bloc. Un bloc est

<footnote n="9">⁹ Voir, à ce propos, au chapitre 3, les exemples donnés avec la classe String, paragraphe 3.3.</footnote>
un objet syntaxique qui permet la construction de schémas itératifs ou conditionnels. Il contient toujours une séquence d'expressions dont il déclenchera l'évaluation chaque fois qu'il recevra un message correspondant à l'une des trois méthodes *value*, *value*: ou *value*:value*. Examinons par exemple la séquence de la figure 2.10 qui calcule, dans la variable somme, la somme des valeurs du tableau d'entiers tableau.

```
|tableau somme|
tableau := # (1 10 100 1000).
somme := 0.
tableau do: [ : element | somme := somme + element ] .
```

<caption>Figure 2.10 : exemple d'évaluation d'un bloc à un argument</caption>

A la dernière ligne de la séquence présentée à la figure 2.10, l'objet tableau reçoit un message de la forme *do: blocAvecUnArgument*. Le traitement d'un tel message déclenche une itération qui évalue *blocAvecUnArgument* pour chaque élément du tableau. Le bloc évalué est ici :

```
[:element | somme := somme + element]
```

Il comprend deux parties encadrées par [ et ] et séparées par une barre verticale. La première déclare l'argument *element* qui est une variable temporaire, locale au bloc, qui sera créée au début de l'évaluation du bloc et supprimée en fin d'évaluation. Si *o* est un objet et si le bloc reçoit le message *value: o*, la variable *element* est, lors de sa création, initialisée de manière à désigner l'objet *o*. Ainsi, le traitement du message *do*: de la dernière ligne de la figure 2.10 va amener l'objet *(1 10 100 1000)* à envoyer au bloc successivement les quatre messages *value: 1*, *value: 10*, *value: 100* et *value: 1000* qui créeront puis supprimeront quatre fois la variable *element* pour lui faire désigner successivement les entiers *1*, *10*, *100* et *1000*. A la fin d'exécution de la séquence de la figure 2.10, la variable somme aura la valeur *1111*.

Un argument de bloc est donc une variable temporaire, locale au bloc dans lequel elle est déclarée. Sa déclaration doit figurer en tête du bloc entre les signes [ et | et elle est immédiatement précédée par le caractère : (deux points). Elle a les mêmes caractéristiques que celles des variables temporaires décrites en 2.8.1 mais elle reste liée à l'objet qu'elle représente et qui est l'argument du message *value*: ou *value:*:value:*<fnref n="10" /> envoyé au bloc. En d'autres termes, il n'est pas possible, dans un bloc avec argument, d'affecter directement une valeur à l'argument. Ainsi, un bloc dont la forme est [*:arg/*...] ne peut contenir une expression de la forme *arg := v*. En revanche, l'objet que représente l'argument de bloc peut être éventuellement modifié, si l'on envoie à cet argument (i.e. à cet objet) le message adéquat.

<footnote n="10">10 Sauf extension apportée par le programmeur au langage, Smalltalk V ne prévoit que des blocs à zéro, un ou deux arguments évaluables avec les messages *value* (pas d'argument), *value*: (un argument) ou *value*:value: (deux arguments).</footnote>
### 2.8.3 Les arguments d'une méthode

Un argument d'une méthode *m* est une variable temporaire qui représentera un objet qui interviendra dans l'évaluation de *m* et qui est déclaré, dans l'en-tête de *m*, comme paramètre. Par exemple, dans l'évaluation de :

```python
'Bonjour' printOn: Transcript
```

qui fera apparaître la chaîne 'Bonjour' dans la fenêtre *System Transcript*, la méthode *printOn*., de la classe *String*, est utilisée, conformément au protocole indiqué à la figure 2.11.

```python
printOn: aStream
  "Append the receiver as a quoted string to aStream doubling
   all internal single quote characters."
aStream nextPut: $'.
self do:
    [:character |
     aStream nextPut: character.
     character = $' ifTrue: [aStream nextPut: character]
    ].
aStream nextPut: $
```

<caption>Figure 2.11 : la méthode *printOn* : de la classe *String*</caption>

Dans cette méthode, la variable *aStream* est l'argument qui représentera l'objet utilisé en paramètre d'un message *printOn*: envoyé à un objet de la classe *String*. L'objet représenté par l'argument d'une méthode peut être modifié s'il reçoit, au cours de l'exécution du protocole de cette méthode, un message approprié. C'est le cas, pour l'évaluation de 'Bonjour' *printOn: Transcript*.: En effet, l'argument *aStream* correspond à la fenêtre *Transcript* et cette fenêtre est modifiée neuf fois, à chaque évaluation d'un message *nextPut*: qui lui est adressé.

On notera également que, comme l'argument d'un bloc, l'argument d'une méthode reste lié à l'objet-paramètre qui lui correspond : une affectation d'un nouvel objet à un tel argument est illicite. Enfin, on remarquera que la figure 2.11 offre également un exemple de l'évaluation d'un bloc avec un argument. En effet, l'objet-récepteur du message *printOn* (ici 'Bonjour') déclenchera, avec un message *do*: (cinquième ligne de la figure), une évaluation répétitive du bloc dont l'argument est la variable *character*. Dans cette évaluation répétitive, l'argument *character* prendra successivement les valeurs $B, $o, $n, $j, $o, $u et $r. Remarquons également que ce bloc contient lui-même un autre bloc : [*aStream nextPut: character*] qui est l'argument du message *ifTrue*: (cf 2.11.1).

## 2.9 - Les variables partagées

Les variables partagées sont des variables permanentes qui sont accessibles à plusieurs objets. A la différence des variables temporaires, leur noms commencent toujours par une majuscule. Pour certaines d'entre elles, qui sont définies par
l'utilisateur, la « durée de vie » est déterminée par ce même utilisateur, qui peut, à tout moment, les supprimer de l'environnement de Smalltalk.

D'autres variables partagées sont prédéfinies et ne peuvent être supprimées. Par exemple, la variable *Smalltalk* désigne un objet de la classe *Dictionary* qui répertorie la plupart des objets manipulés par Smalltalk et que nous étudierons au chapitre 3 (cf 3.5). Egalement, la variable *CharacterConstants* désigne un objet qui répertorie les symboles descriptifs qui peuvent être utilisés pour représenter des caractères spéciaux (*Bell*, *Cr*, *Tab*, *MouseButton*, etc, cf 2.9.2).

Les variables partagées peuvent être classées en trois catégories : variables globales, dictionnaires partagés (en Anglais, pool dictionaries) et variables de classe. Nous allons maintenant étudier chacune de ces catégories.

## 2.9.1 - Les variables globales

Une variable globale désigne un objet qui est accessible à tous les objets<fnref n="11" />. Par exemple, la variable *Transcript*, que nous avons déjà fréquemment utilisée, est un objet de la classe *TextEditor* dont la fenêtre (*System Transcript*) peut faire apparaître des informations envoyées par tout objet de l'environnement Smalltalk.

La variable globale *Transcript* est une variable prédéfinie dans l'environnement de Smalltalk. L'utilisateur peut aussi définir de nouvelles variables globales. Etudions par exemple l'évaluation de la séquence de la figure 2.12.

```
|t|
t := 25.
TauxTVA := # (5.5 18.6 33).
Repertoire CourantDeD := Directory new
  drive: $D;
  pathname: '\'.
TauxTVA at: 3 put: t.
```

<caption>Figure 2.12 : utilisation de variables globales</caption>

Après l'évaluation de l'expression de la deuxième ligne, la variable temporaire *t* désigne l'objet 25. Pour évaluer la ligne suivante, Smalltalk va procéder en deux temps :

- Ayant constaté que *TauxTVA* ne peut être qu'une variable globale (car son nom commence par un majuscule) et que cette variable est encore inconnue, Smalltalk va interroger l'utilisateur, en utilisant un menu à deux options. Ce menu permet à l'utilisateur, soit de renoncer à la création de la variable globale<fnref n="12" />, soit de confirmer cette création.

- Si nous supposons que, dans l'évaluation de la figure 2.12, l'utilisateur confirme la création de *TauxTVA*, Smalltalk réserve alors un emplacement pour cette variable puis l'initialise pour lui faire désigner un

<footnote n="11">11 Au contraire, les dictionnaires partagés et les variables de classe ne sont accessibles qu'à certaines catégories d'objets (cf 2.9.2 et 2.9.3).</footnote>

<footnote n="12">12 Il le fera par exemple si la variable correspond à une variable temporaire dont il aura, par erreur, frappé l'initiale du nom en majuscule.</footnote>
tableau de trois éléments (qui représentent trois des taux de TVA les plus utilisés en France en 1989).

La poursuite de l'évaluation de la séquence de la figure 2.12 va conduire Smalltalk à créer une nouvelle variable globale, *RépertoireCourantDeD*<fnref n="13" />, qui représentera un objet de la classe *Directory* et désignera dans un environnement MS-DOS, le répertoire-racine du disque *D*:

Enfin, l'évaluation de la dernière expression de la figure 2.12 modifiera le dernier élément de la variable globale *TauxTVA* pour remplacer le taux de TVA de 33% par la valeur de *t* (25%). A l'issue de l'exécution de la séquence complète, la variable temporaire *t* disparaîtra de l'environnement de Smalltalk mais les variables globales *TauxTVA* et *RépertoireCourantDeD* resteront disponibles. On notera que la disparition de la variable *t* n'entraînera pas de modification de la valeur du troisième élément de *TauxTVA*. En effet, l'évaluation de l'expression *TauxTVA at: 3 put: t* (cf figure 2.12) avait affecté au troisième élément du tableau *TauxTVA* l'objet représenté par *t*: cet élément pointe désormais vers l'entier 25. La disparition de *t* supprime la valeur de *t*, c'est-à-dire le pointeur, présent dans *t*, vers l'entier 25. Le pointeur vers ce même entier, présent dans le troisième élément de *TauxTVA*, lui, ne sera pas modifié. L'entier 25 restera présent dans l'environnement de Smalltalk tant qu'un pointeur le désignera.

Après l'évaluation de la séquence de la figure 2.12, les variables *TauxTVA* et *RépertoireCourantDeD* subsistent dans l'environnement de Smalltalk. On peut les modifier, par exemple en évaluant la séquence :

```
TauxTVA := #5.5 14 18.6 25).
RepertoireCourantDeD pathname: '\Marie\Musique'
```

La première expression évaluée affectera un nouvel objet à la variable *TauxTVA*, ajoutant ainsi un taux de TVA intermédiaire de 14% entre 5,5% et 18,6%. A l'évaluation de cette première expression, Smalltalk n'interrogera plus l'utilisateur pour valider la création de la variable globale, puisque cette variable lui est déjà connue. Ensuite, l'évaluation de la deuxième expression modifiera l'objet désigné par *RepertoireCourantDeD* pour désigner le répertoire *Marie\Musique* sur le disque *D*:

Comment supprimer une variable globale créée par l'utilisateur ? Smalltalk répertorie toutes les variables globales dans le dictionnaire *Smalltalk*, que nous avons mentionné au début du paragraphe 2.9. Pour supprimer une variable globale, il suffit d'envoyer à ce dictionnaire un message *removeKey*: dont l'argument est un symbole qui représente le nom de la variable<fnref n="14" />. Ainsi, pour supprimer, de l'environnement de Smalltalk, la variable *TauxTVA*, on évaluera :

```
Smalltalk removeKey: #TauxTVA
```

## 2.9.2 - Les dictionnaires partagés

Un dictionnaire partagé (en Anglais, pool dictionary) est un objet de la classe *Dictionary* qui répertorie des informations que l'on peut partager entre plusieurs classes. Chacune de ces classes doit, dans sa définition, indiquer explicitement

<footnote n="13">13 Sous réserve, bien entendu, que l'utilisateur confirme son accord pour la création.</footnote>

<footnote n="14">14 La gestion des informations d'un dictionnaire sera étudiée au chapitre 3.</footnote>
une référence à ce dictionnaire partagé. Par exemple, la définition, en Smalltalk V, de la classe *FEleStream* :

```
ReadWriteStream subclass: #FEleStream
instanceVariableNames:
    'file pageStart writtenOn lastByte lineDelimiter '
    classVariableNames: ''
poolDictionaries:
    'CharacterConstants '
```

indiquera que cette classe utilise le dictionnaire partagé *CharacterConstants* que nous avons mentionné au début du paragraphe 2.9 et qui répertorie les symboles descriptifs qui peuvent être utilisés pour représenter des caractères spéciaux (Bell, Cr, Tab, MouseButton, etc).

### 2.9.3 - Les variables de classe

Une variable de classe est une variable qui permet le partage d'informations entre toutes les instances d'une même classe. Elle fait partie de la définition de la classe et elle est accessible à la classe ainsi qu'à toutes les instances de la classe.

A titre d'exemple, supposons que, pour une application donnée, nous utilisons une classe *Vehicule*, dont les instances décrivent les véhicules de service d'une entreprise. Pour connaître l'état du parc, on utilisera deux variables de classe. Elles apparaissent dans la définition de la classe qui est donnée, en première partie de la figure 2.13.

**Définition de la classe Vehicule**

```
Object variableSubclass: #Vehicule
instanceVariableNames:
    'type annee kilometrage '
    classVariableNames:
        'NombreDeVehicules VehiculesSortis '
poolDictionaries: ''
```

**Deux méthodes de classe de la classe Vehicule**

**TousAuGarage**

```
"A la réception de ce message, la classe Vehicule remet à zéro la variable de classe VehiculesSortis pour indiquer que tous les véhicules sont au garage"
VehiculesSortis := 0
```

**VehiculesSortis**

```
"A la réception de ce message, la classe Vehicule renvoie la valeur de la variable de classe VehiculesSortis"
^VehiculesSortis
```

<caption>Figure 2.13 : deux variables de classe dans la définition d'une classe</caption>
La variable de classe *NombreDeVehicules* aura pour rôle d'indiquer le nombre de véhicules du parc de l'entreprise et la variable de classe *VehiculesSortis* indiquera le nombre de véhicules qui sont en train d'effectuer un déplacement. On notera la différence par rapport aux variables d'instance *type*, *année* et *kilometrage* : il n'y a qu'un seul exemplaire de chacune des deux variables *NombreDeVehicules* et *VehiculesSortis* et ces variables sont permanentes. En revanche, chaque instance de la classe *Vehicule* dispose d'un jeu des trois variables *type*, *année* et *kilometrage*, dont la durée de vie est celle de l'instance elle-même. Par exemple, dans l'évaluation de la séquence suivante :

```
|fourgon1|
fourgon1 := Vehicule new.
fourgon1 sort. "sort est une méthode d'instance de la classe Vehicule"
" ... ici des déplacements avec fourgon1 ..."
fourgon1 rentre "rentre est une méthode d'instance de la classe Vehicule"
```

trois variables d'instance *type*, *année* et *kilometrage* seront créées au moment de l'évaluation de *fourgon1* := *Vehicule new*. Elles disparaîtront de l'environnement, avec la variable temporaire *fourgon1*, à l'issue de l'évaluation de la séquence.

Une variable de classe de la classe C est accessible à la classe C et à ses sous-classes, mais pas aux autres classes. Si V est une variable de classe de la classe C, C peut modifier ou consulter V, par l'intermédiaire de méthodes de classe. Par exemple, si nous revenons à la figure 2.13, la deuxième partie de cette figure présente deux méthodes de classe de la classe *Vehicule*. On utilisera par exemple la première, en début d'application, pour remettre à zéro la variable *VehiculesSortis*<fnref n="15" />. La deuxième pourra servir à donner une information sur les véhicules, par exemple en évaluant :

```
Transcript nextPutAll: 'nombre de véhicules à l''extérieur :'.
(Vehicule VehiculesSortis) printOn: Transcript
```

De même, pour permettre à une instance de la classe *Vehicule* de modifier *VehiculesSortis*, il faudra utiliser une méthode *d'instance*. On peut par exemple supposer que le message *sort envoyé*, dans un précédent exemple, à l'instance *fourgon1* correspond à la méthode suivante :

```
sort
  "le véhicule-récepteur sort du garage"
VehiculesSortis := VehiculesSortis + 1
  ... "autres opérations liées à la sortie"
```

Réciproquement, la méthode *d'instance rentre* devra diminuer d'une unité la valeur de *VehiculesSortis*, pour comptabiliser le retour du véhicule.

## 2.10 - La classe Context et l'évaluation d'un bloc

Nous avons déjà abordé la notion de bloc au paragraphe 2.8.2. Précisons ici ses caractéristiques en les rattachant à la classe *Context*. Les propriétés de cette

<footnote n="15">15 Dans l'environnement de Smalltalk, une variable de classe est une variable permanente qui conserve sa valeur, entre deux exécutions de Smalltalk. Lors de la définition d'une nouvelle classe, toutes les variables d'instance de cette classe sont initialisées à la valeur nil.</footnote>
classe permettent, en Smalltalk, de définir, dans le protocole d'une méthode, des opérations conditionnelles ou itératives.

### 2.10.1 - Un bloc est un objet

Distinguons tout d'abord entre séquence d'expressions et bloc. Si nous évaluons, avec *show it*, la séquence :

```
| x y z |
x := 1. y := 2. z := 3
```

Smalltalk répond en affichant la valeur 3. Dans l'évaluation d'une séquence, la valeur finale est celle de la dernière expression évaluée (ici, celle de l'entier affecté à la variable temporaire z).

Reprenons maintenant la même séquence, mais en encadrant les trois affectations par une paire de crochets [ et ]. La séquence comprend maintenant une déclaration de trois variables, suivie d'un bloc (un bloc est une suite d'expressions, encadrée par des crochets). La séquence ainsi obtenue, que nous appellerons séquence 1, est présentée à la figure 2.14, sur la fenêtre de gauche. Cette fenêtre montre, en inversion vidéo, le résultat de l'évaluation de la séquence 1. Ici, le résultat n'est plus 3 : Smalltalk répond en indiquant que ce résultat est un objet de la classe *Context*. En effet, un bloc est un objet de la classe *Context* : sa valeur est celle de l'objet lui-même, c'est-à-dire la séquence d'expressions entre crochets (et non le résultat de l'évaluation de cette séquence). En fin d'évaluation de la séquence 1, Smalltalk renverra donc la dernière valeur traitée, c'est-à-dire le bloc lui-même.

<figure><img src="image_11.png" /><figcaption>Figure 2.14 : Un bloc s'évalue quand il reçoit le message *value*</figcaption></figure>

Evaluer un bloc, c'est évaluer la séquence d'expressions représentée par ce bloc. Un bloc étant un objet, il est seul habilité à déclencher sa propre évaluation : il le fera quand il recevra le message *value* et la valeur renvoyée sera celle de la dernière expression évaluée. Si nous revenons à la figure 2.14 et si nous examinons la fenêtre de droite, nous constatons que la séquence 2 diffère de la séquence 1 par l'envoi du message *value* au bloc `[x:=1. y:=2. z:=3]`. Le résultat
de l'évaluation de la séquence 2 est donc celui de l'évaluation du bloc : il apparaît en inversion vidéo, au bas de la fenêtre. C'est l'entier 3, valeur de la dernière expression évaluée dans le bloc.

Notons que, si un bloc fait partie du protocole d'une méthode, il peut contenir une expression de la forme :

^expressionFinale

qui, si elle est évaluée, terminera à la fois l'évaluation du bloc (même si elle n'est pas la dernière expression de ce bloc) et l'exécution de la méthode, en renvoyant la réponse *expressionFinale*.

### 2.10.2 - Bloc récursif

Un bloc est récursif s'il s'envoie lui-même le message *value*. Cette caractéristique sera utilisée pour la construction de schémas itératifs (cf 2.12). Etudions-la ici, à travers l'exemple suivant.

```R
"séquence R"
entier bloc somme
entier:=0. somme :=0.
bloc := [entier:=entier+1.
        somme:=somme+entier.
        (entier=4) ifTrue: [somme]
               ifFalse: [bloc value]
        ].
bloc value
```

La séquence R utilise trois variables temporaires : *entier*, *somme* et *bloc*. Quand on l'évalue, les variables *entier* et *somme* sont d'abord initialisées avec l'entier 0 puis la variable *bloc* désigne un bloc. Dans ce bloc, les deux premières expressions augmentent *entier* de 1 puis cumulent la valeur ainsi obtenue dans la variable *somme*. La dernière expression du bloc est conditionnelle : si *entier* est différent de 4, elle déclenche une nouvelle évaluation de *bloc* (avec l'évaluation de *bloc value*), sinon elle revoie la valeur de la variable *somme*. La dernière ligne de la séquence R est celle qui déclenchera la première évaluation de *bloc*. Celle-ci cumulera 1 dans *somme* puis déclenchera une deuxième évaluation de *bloc*, qui, à son tour cumulera 2 dans *somme*, etc jusqu'à ce que la variable *entier* atteigne la valeur 4. L'évaluation de la séquence R sera alors terminée et renverra la valeur 10 de *somme*, qui correspond à la somme des quatre premiers entiers.

Toujours à propos de l'exemple précédent, notons que des blocs peuvent être emboîtés. Le bloc récursif de la séquence R, contient deux autres blocs : [*somme*] et [*bloc value*] dont la présence est imposée par la méthode *ifTrue:ifFalse:*. qui exige que ses arguments soient des blocs (cf 2.11).

### 2.10.3 - Blocs avec arguments

Les blocs que nous venons d'étudier sont des blocs sans arguments. Un bloc peut comporter des arguments qui sont des variables temporaires, déjà présentées en 2.8.2. On les déclare en tête du bloc, entre le crochet gauche [ et une barre de
déclaration |, chaque nom d'argument étant immédiatement précédé du caractère : (deux points). Par exemple, un bloc avec un argument aura la structure suivante :

[:argument| sequenceExpressions]

Un tel bloc s'évaluera quand il recevra le message *value: uneValeur* qui déclen-
chera l'évaluation de *sequenceExpressions*, dans laquelle la variable *argument*
prendra la valeur *uneValeur*. Le lecteur pourra se reporter à l'exemple de la figure
2.10, paragraphe 2.8.2, pour étudier une telle évaluation.

En Smalltalk V, un bloc peut comporter zéro, un ou deux arguments. Un bloc
à deux arguments s'évalue quand il reçoit le message :

value: premierArgument value: deuxièmeArgument

qui correspond à la méthode value:value: de la classe Context.

2.11 - Les classes True et False et la réalisation d'un choix

Dans la hiérarchie des classes, les classes *Boolean*, *True* et *False* rassemblent les objets logiques qui permettent les choix dans l'évaluation d'une séquence d'expressions. La classe *Boolean* est une classe abstraite, classe-mère des deux sous-classes *True* et *False*. La classe *True* est une classe qui possède une seule instance, permanente dans l'environnement de Smalltalk. Cette instance est l'objet *true* qui représente la valeur logique « vrai ». De même, la classe *False* a une seule instance, l'objet *false*. La classe *Boolean* définit des méthodes générales dont héritent *True* et *False*, en particulier la méthode *new*, redéfinie pour éviter l'héritage de la classe *Object*. Cette méthode est, en Smalltalk V, définie de la manière suivante :

new

"Disallow the instantiation of booleans because there is only one true and one false."

^self invalidMessage

Son exécution provoquera l'apparition d'une fenêtre de diagnostic indiquant
que le message new n'est pas accepté par la classe : comme on l'imagine, il n'est
pas possible de créer de nouveaux objets logiques, distincts de true et false.

2.11.1 - Schémas conditionnels

Examinons la séquence suivante qui permet de calculer la racine carrée d'un
entier positif frappé au clavier par l'utilisateur<fnref n="16" />.

|entier|
entier := (Prompter
prompt: 'je calcule la racine carrée de l''entier:'
default: '') asInteger.
entier ≥ 0 ifTrue: [entier sqrt]

<footnote n="16">16 La classe Prompter, utilisée dans cet exemple, offre une interface élémentaire de dialogue avec l'utilisateur. Adressé à cette classe, le message prompt: question default: réponse affiche une question à l'écran et propose une réponse par défaut. Nous reviendrons sur la classe Prompter en 3.5.2. Pour un objet de la classe Prompter, la méthode prompt: default: renvoie toujours le texte frappé par l'utilisateur sous la forme d'un objet de la classe String.</footnote>
L'évaluation, avec *show it*, de cette séquence fait d'abord apparaître à l'écran une fenêtre de type *Prompter* qui demande à l'utilisateur de frapper un entier et rend cet entier sous la forme d'une chaîne. Cette chaîne reçoit le message *asInteger* et renvoie l'entier correspondant, qui devient la valeur de la variable temporaire *entier*. Ensuite Smalltalk évalue l'expression *entier ≥ 0* qui, en fonction du signe de l'entier traité, renverra l'objet *true* ou l'objet *false*. Cet objet reçoit alors le message :

```
ifTrue: [entier sqrt]
```

dont le traitement sera différent selon que l'objet récepteur sera *true* ou *false*. Si, par exemple, l'entier choisi était 169, alors l'objet récepteur est *true* et le bloc [*entier sqrt*] est évalué et renvoie 17 la valeur 13.0. Si, au contraire, l'entier choisi était -10, alors l'objet récepteur est *false* et l'évaluation de la méthode *ifTrue*: renvoie *nil*.

On constate que la méthode *ifTrue*:, qui correspond au schéma algorithmique si ... alors ..., provoque un traitement différent selon que l'objet récepteur est *true* ou *false*. Ce polymorphisme de la méthode *ifTrue*: est assuré, pour les sous-classes de *Boolean*, par une définition de la méthode dans chacune des classes *True* et *False*, comme le montre la figure 2.15.

pour la classe True
```
ifTrue: aBlock
    "If the receiver is true, answer the result of evaluating aBlock (with no arguments),
    else answer nil."
    ^ aBlock value
```

pour la classe False
```
ifTrue: aBlock
    "If the receiver is true, answer the result of evaluating aBlock (with no arguments),
    else answer nil."
    ^ nil
```

<caption>Figure 2.15 : la méthode *ifTrue*: est définie pour les classes *True* et *False*</caption>

On voit donc comment la distinction entre les deux classes *True* et *False* permet de définir simplement un schéma si ... alors .... De la même manière, l'alternative si ... alors ... sinon ... sera représentée par l'envoi, à un objet logique, du message *ifTrue: trueBlock ifFalse: falseBlock* pour lequel la méthode correspondante de la classe *True* renverra *trueBlock value* et celle de la classe *False* renverra *falseBlock value*.

### 2.11.2 - Opérateurs logiques

Smalltalk définit, pour les classes *True* et *False*, des méthodes correspondant aux opérateurs logiques *non*, *et* *ou*. Ainsi la méthode unaire *not* renvoie *false* pour la classe *True* et *true* pour la classe *False*: *(3 > 1) not s'évalue false*.

<footnote n="17">17 Quand un entier non négatif reçoit le message sqrt, il renvoie sa racine carrée, sous la forme d'un réel (instance de la classe float).</footnote>
La conjonction est représentée par deux méthodes qui diffèrent par la nature de leurs arguments :

- la méthode binaire `& aBoolean` prend comme argument un objet de la classe `True` ou `False`. Elle renvoie `aBoolean` pour la classe `True` et `false` pour la classe `False`.

- la méthode à mot-clé `and: aBlock` prend comme argument un bloc, dont le résultat de l'évaluation doit être un objet de la classe `True` ou `False`.

L'exemple suivant montre la différence d'écriture :

(3 > 1) & ('abc' reversed = 'cba') "s'évalue true"

(3 > 1) and: ['abc' reversed = 'cba'] "s'évalue true"

Smalltalk V ne fait pas d'autre distinction entre ces deux méthodes que celle qui concerne la nature des arguments<fnref n="18" />.

D'une manière analogue, on pourra pour la disjonction non exclusive, utiliser la méthode binaire `| aBoolean` ou la méthode `or: aBlock`. Le ou exclusif correspond à la seule méthode `xor: aBoolean`, dont l'argument n'est pas un bloc mais un objet de la classe `True` ou `False`.

## 2.12 - La classe Context et les itérations.

Smalltalk dispose d'un mécanisme général d'itération qui est représenté par les méthodes `whileTrue:` et `whileFalse:` de la classe `Context`. La méthode `whileTrue:` est présentée à la figure 2.16.

```Smalltalk
whileTrue: aBlock
  "Repetitively evaluate the receiver block and aBlock, until the result of receiver block
evaluation is false. Answer nil."
  self value
  ifTrue: [aBlock value.
          self whileTrue: aBlock].
^nil
```

<caption>Figure 2.16 : la méthode `whileTrue:` de la classe `Context`</caption>

Cette méthode correspond au schéma classique tant que prédicat faire action(s) et s'utilise dans un énoncé de la forme :

`blocCondition whileTrue: blocRépété`

L'objet `blocCondition` reçoit le message `whileTrue`. Cet objet est un bloc, instance de la classe `Context`, dont l'évaluation doit renvoyer l'objet `true` ou l'objet `false`<fnref n="19" />.

<footnote n="18">18 En revanche, Smalltalk-80 considère le `&` comme "évaluatif" (`aBoolean` est toujours évalué même si le récepteur est l'objet `false`) et le `and`: comme non forcément évaluatif.</footnote>

<footnote n="19">19 Si l'évaluation de blocCondition ne renvoie pas un objet logique, l'exécution de la méthode `whileTrue:` affichera une fenêtre de diagnostic avec l'indication d'erreur "receiver is not a boolean" car l'objet renvoyé par l'évaluation de blocCondition reçoit le message `ifTrue:` (cf figure 2.15).</footnote>
En effet, le protocole de la méthode `whileTrue`: envoie ensuite à cet objet logique un message `ifTrue`: qui, si l'objet est `true`, déclenche deux opérations successives :

- `blocRépété` est évalué ;

- `whileTrue: blocRépété` est à nouveau envoyé à l'objet `blocCondition`: cette récursivité terminale réalise le mécanisme d'itération.

La méthode `whileFalse`: est conçue selon le même principe, l'itération ne se poursuivant cette fois que tant que l'évaluation de `blocCondition` renvoie `false`.

Avec ces schémas généraux, on peut décrire pratiquement toutes les formes d'itération. En particulier, on peut, dans une itération incluse dans le protocole d'une méthode, provoquer un arrêt de l'itération et une terminaison immédiate de la méthode en évaluant une expression de renvoi de la forme `^valeurRetournée`. A titre d'exemple, définissons, pour la classe `String`, la méthode `estPalindrome` dont le message unaire, adressé à une chaîne, permettra de savoir, si cette chaîne est, ou non, un palindrome, c'est-à-dire si elle se lit de la même manière dans les deux sens (« ici », « elle » sont des palindromes ; le mot « elles » n'en est pas un).

Une manière simple de résoudre le problème revient à observer qu'un palindrome ne change pas si l'on inverse l'ordre de ses caractères. On peut donc utiliser la méthode `reversed` et proposer la définition suivante, pour la méthode cherchée :

```
estPalindrome
    "répond true si la chaîne réceptrice est un palindrome"
    ^ (self = self reversed)
```

Une telle définition est conforme à l'esprit de la programmation orientée objets qui doit être de rechercher systématiquement la réutilisation de code. Ici, on s'appuie sur la méthode `reversed` pour construire la méthode `estPalindrome`. Dans une utilisation pour le prototypage, fréquente avec Smalltalk, on ne peut qu'encourager une telle pratique. Cependant, sur l'exemple précis de la méthode `estPalindrome`, si l'on recherche la rapidité d'exécution, on peut remarquer que pour une chaîne dont le premier et le dernier caractères sont différents, l'utilisation de `reversed` inversera d'abord la chaîne, alors qu'une comparaison des caractères en partant des deux extrémités permettrait de conclure plus rapidement. La deuxième version de `estPalindrome`, présentée à la figure 2.17, est conçue selon ce principe.

```
estPalindrome
    "répond true si le récepteur est un palindrome ou une chaîne vide ;
    version 2 : comparaison caractère par caractère, en partant des deux extrémités"
    |gauche droite| "désigneront les caractères comparés"
    gauche :=1. droite := self size.
    [gauche < droite]
        whileTrue: [(self at:gauche) =~ (self at:droite)
                      ifTrue: [^false].
                      gauche := gauche + 1.
                      droite := droite - 1.].
    ^ true
```

<caption>Figure 2.17 : on sort de l'itération dès qu'une différence est constatée</caption>
Dans cette version, l'évaluation de `^false`, dès qu'une différence entre deux caractères est constatée, provoque à la fois la sortie de l'itération et la terminaison de la méthode.

Smalltalk utilise la méthode `whileTrue`: dans de nombreuses méthodes itéra-tives définies pour la classe *Collection* et ses sous-classes, qui permettent d'effec-tuer un traitement sur l'ensemble des éléments d'une collection ou sur une sélec-tion de ces éléments (cf chapitre 4).

Pour terminer, signalons un dernier schéma itératif, disponible avec la métho-de *timesRepeat*: de la classe *Integer*. Cette méthode s'emploie dans une expres-sion de la forme :

`unEntier timesRepeat: unBloc`

et déclenchera si `unEntier` est positif, `unEntier` répétitions de l'évaluation de `unBloc`. La figure 2.18 donne le protocole de cette méthode ainsi qu'un exemple d'utilisation.

**La méthode timesRepeat:**

```
timesRepeat: aBlock
    "Evaluate the block aBlock n number of times, where n is the receiver."
    | anInteger |
    anInteger := self.
    [anInteger > 0]
    whileTrue: [anInteger := anInteger - 1.
                aBlock value]
```

**Exemple d'utilisation**

l'évaluation de la séquence suivante
"calcul, dans p, de 2 puissance 32"
`|p| p:=1. 32 timesRepeat: [p := p*2].`

p
donne, avec *show it*, le résultat `4294967296`

<caption>Figure 2.18 : la méthode *timesRepeat*.</caption>
# 3 - Aller plus loin avec Smalltalk

## 3.1 - Faire des calculs

Comme tout environnement de programmation, Smalltalk fournit des primitives pour manipuler des nombres. Tous ces nombres sont des instances de sous-classes de la classe *Number*, dont la descendance est représentée à la figure 3.1. La classe *Number* elle-même est une sous-classe de la classe *Magnitude*, qui rassemble des méthodes permettant de comparer des grandeurs mesurables et que nous avons abordée en 1.6 (Cf figure 1.8).

<figure><img src="image_12.png" /><figcaption>Figure 3.1 : la classe *Number* et sa descendance</figcaption></figure>

Un nombre en Smalltalk est toujours une instance de l'une des classes *Float*, *Fraction*, *LargeNegativeInteger*, *LargePositiveInteger* ou *SmallInteger*. Toutes ces classes héritent de la classe abstraite *Number*, qui rassemble des méthodes générales qui s'appliqueront à toutes les sous-classes comme, par exemple, la méthode *squared* qui renvoie le carré du récepteur :

### squared

"Answer the receiver multiplied by the receiver."

^ self * self
Ces méthodes générales font souvent appel aux opérations arithmétiques de base (addition, soustraction, multiplication et division) qui, elles, sont toutes définies dans la classe *Number* par le protocole `^implementedBySubclass` car elles doivent impérativement être redéfinies pour chaque sous-classe de *Number*. C'est cette redéfinition qui assurera le polymorphisme de méthodes telles que *squared*.

### 3.1.1 - La représentation littérale des nombres

La manière la plus courante de représenter un nombre en Smalltalk est d'écrire une suite de chiffres décimaux, précédés éventuellement du signe `-` (moins), et qui peuvent être suivis d'un point et d'une autre suite de chiffres décimaux :

5 512 -53 345.12 -5.0

On notera qu'un nombre positif s'écrira toujours sans signe car Smalltalk considère systématiquement le signe `+` comme le sélecteur de la méthode binaire d'addition. Ainsi, Smalltalk répondra par l'indication d'erreur *invalid receiver* à l'évaluation de `+50`. En revanche, Smalltalk distingue entre la méthode binaire de soustraction `–` et le signe `-`, en imposant au signe d'être immédiatement suivi d'un chiffre : l'évaluation de `-50` renvoie `-50` mais l'évaluation de l'expression `– 50` renvoie *invalid receiver*.

On peut aussi représenter un nombre en utilisant la notation scientifique `<m>e<p>` où `<m>` est un nombre tel que ceux définis précédemment et `<p>` un exposant entier, positif ou négatif. Le nombre représenté est celui obtenu en multipliant la valeur de `<m>` par la `<p>`-ième puissance de la base (en général 10) : l'écriture `3.45e-2` représente la valeur 0,0345 ; `12e193` représente l'entier $12 \times 10^{193}$. On notera que l'exposant est obligatoirement introduit par la lettre minuscule e car la majuscule E représente le « chiffre » 14 dans les systèmes de numération à base supérieure à 14.

En effet, on peut aussi représenter des nombres en les écrivant dans une base différente de 10. Dans ce cas, la représentation du nombre est précédée du préfixe `<b>r` (r pour « radix », base en Anglais), l'entier `<b>` indiquant la base. A l'évaluation, Smalltalk renverra toujours une écriture en base 10. Pour représenter les chiffres supérieurs à 9 (dans une base plus grande que 10), on utilise les majuscules A (pour 10), B (pour 11), etc. Par exemple :

- l'évaluation de `2r111` renverra 7,

- l'évaluation de `16rA0` renverra 160,

- l'évaluation de `17r1G` renverra 33,

- l'évaluation de `16rA0.8` renverra 160.5.

### 3.1.2 - Entiers, rationnels et réels approchés

Smalltalk classe les nombres en trois catégories avec, pour chacune, une représentation particulière : les entiers (classe *Integer* et ses sous-classes), les valeurs exactes de rationnels non entiers (classe *Fraction*) et les approximations de réels représentés en virgule flottante (classe *Float*).
Les méthodes applicables aux entiers sont rassemblées dans la classe *Integer*, qui comprend trois sous-classes (Cf figure 3.1). La classe *SmallInteger* est celle des entiers pour lesquels l'ordinateur utilisé dispose de circuits de calcul standard<fnref n="1" />. Les autres entiers sont répartis, selon leurs signes, en deux classes : *LargePositiveInteger* et *LargeNegativeInteger* (qui, en Smalltalk V, permettent de traiter des entiers dont la représentation peut occuper jusqu'à 64Ko). En Smalltalk V, pour tous ces entiers, les méthodes de calcul correspondant aux opérations élémentaires d'addition, de soustraction, de multiplication et de division sont définies dans la classe *Integer* sous la forme de primitives écrites en langage d'assemblage, qui font la distinction entre les classes des entiers manipulés et font appel soit aux instructions-machine de calcul (classe *SmallInteger*) soit à des sous-programmes de calcul (autres sous-classes de *Integer*).

La classe *Fraction* est la classe des rationnels non entiers, qui sont représentés de manière exacte par le quotient *n/d*, *n* et *d* étant deux entiers relatifs premiers entre eux. Ainsi *2/3* est une instance de la classe *Fraction* dont l'approximation *6.666667e-1* est elle-même une instance de la classe *Float* (Cf ci-après, paragraphe 3.1.4, pour cette dernière classe).

Chaque objet de la classe *Fraction* a deux variables d'instance, *numerator* et *denominator*, qui déterminent le rationnel représenté. A la différence de la classe *Integer*, qui redéfinit les méthodes de classes héritées pour interdire la création d'instances nouvelles, la classe *Fraction* dispose de plusieurs méthodes de classes qui permettent de créer des objets de cette classe. En effet si, pour les entiers, on peut considérer que toutes les valeurs sont prédéfinies dans l'environnement et peuvent être représentées par des constantes sous l'une des formes décrites en 3.1.1, chaque objet de la classe *Fraction* doit être créé, afin que ses variables d'instances soient représentées. On peut créer un objet de la classe *Fraction* en utilisant les méthodes de classe *new* ou *numerator:denominator*: ou encore, en envoyant à un entier *e*₁ le message *e*₂, dans lequel *e*₂ n'est pas un diviseur de *e*₁. La figure 3.2 donne des exemples de création d'instances de la classe *Fraction*.

```
|f1 f2 f3 f4 e r|
f1 := Fraction new. "f1 désigne l'instance nil/nil de la classe Fraction"
f2 := Fraction numerator: 2 denominator: 3.
      "f2 désigne l'instance 2/3 de la classe Fraction"
f3 := 2/3.
      "f3 désigne l'instance 2/3 de la classe Fraction"
f4 := 8/12.
      "f4 désigne l'instance 2/3 de la classe Fraction"
e := 8/2.
      "e désigne l'entier 4 de la classe SmallInteger"
r := 8/12.0
      "r désigne la valeur 6.666667e-1 de la classe Float"
```

<caption>Figure 3.2 : quatre instances de *Fraction*, un entier, un réel approché</caption>

On remarquera, sur cette figure, que la méthode `/` de la classe *Integer* utilisée avec un argument entier, effectue une simplification éventuelle comme le montre l'affectation de *8/12* à *f4*. La méthode de classe *numerator:denominator*: ne réalise, en revanche, qu'une simple initialisation des variables d'instances.

<footnote n="1">1 de -32767 à +32768, sur la plupart des postes de travail en 1990.</footnote>
Constatons-le, en utilisant, dans le paragraphe suivant, une fenêtre d'inspection sur une instance de la classe *Fraction*.

### 3.1.3 - Inspection d'un objet

Smalltalk permet, chaque fois qu'on le désire, d'examiner un objet pour mieux en connaître la structure ou pour vérifier que l'état de cet objet est bien celui que l'on attendait. Pour cela, il suffit d'envoyer à l'objet que l'on veut examiner le message *inspect*. La méthode correspondante est définie dans la classe *Object* et elle est donc héritée par toutes les classes<fnref n="2" />. L'effet de cette méthode est d'ouvrir, à l'écran, une fenêtre d'inspection sur l'objet-récepteur. Pour nous familiariser avec une telle fenêtre, examinons la figure 3.3 qui présente deux fenêtres d'un écran Smalltalk.

<figure><img src="image_13.png" /><figcaption>Figure 3.3 : inspection d'une instance de la classe *Fraction*</figcaption></figure>

Pour comprendre les informations qui sont présentées sur cette figure, examinons d'abord la fenêtre en arrière-plan, dont la barre de titre indique « Exemple pour le chapitre 3 ». On a sélectionné, dans cette fenêtre l'expression :

(Fraction numerator: 8 denominator: 12) inspect

puis fait apparaître le menu de cette fenêtre et déclenché, avec *do it*, l'évaluation de la séquence. Cette évaluation s'est effectuée en deux temps :

1) La classe *Fraction* reçoit le message *numerator:8 denominator:12* et renvoie une nouvelle instance.

<footnote n="2">2 Sauf pour la classe *Dictionary*, qui redéfinit cette méthode pour adapter la présentation de la fenêtre d'inspection à la structure d'un dictionnaire (Cf 3.5).</footnote>
2) Cette nouvelle instance de la classe *Fraction* reçoit le message *inspect* et ouvre une fenêtre d'inspection sur elle-même. C'est cette fenêtre, dont la barre de titre indique « inspecting Fraction », qui est la fenêtre active.

Une fenêtre d'inspection comporte deux panneaux. Le panneau de gauche présente une liste, dont les éléments désignent dans l'ordre, l'objet *inspecté* (*self*) et ses variables d'instances (désignées par leurs noms ou par leurs numéros s'il s'agit de variables indexées). On peut sélectionner un des éléments de cette liste, en cliquant sur le bouton gauche de la souris. On fait alors apparaître la description complète de l'élément dans le panneau de droite. Sur l'exemple de la figure 3.3, l'objet lui-même est sélectionné (*self*) et l'on voit bien, dans le panneau droit, que l'instance de la classe *Fraction* créée par la méthode *numerator:denominator*: n'a pas été simplifiée, puisque l'instance 8/12 est affichée (au lieu de 2/3).

### 3.1.4 - La classe Float

La classe *Float* est celle des nombres dont la représentation interne est en virgule flottante (avec mantisse et exposant) et pour lesquels les calculs nécessitent, soit un coprocesseur de calcul en virgule flottante, soit des sous-programmes d'émulation. Les constantes numériques dont les règles d'écriture ont été données en 3.1.1, sont de la classe *Float* si leur représentation contient un point décimal, sinon elles sont traitées comme des instances de la classe *Fraction* ou *Integer*. Ainsi l'évaluation de

- 1.35 class renvoie *Float*,

- 15e-130 class renvoie *Fraction*,

- 15e130 class renvoie *LargePositiveInteger*.

Pour les opérations arithmétiques élémentaires (Cf 3.1.5), les règles qui déterminent le mode de calcul à appliquer quand une expression contient des valeurs de classes différentes, sont appliquées, en Smalltalk V, directement dans les primitives de calcul définies en langage d'assemblage pour une plus grande efficacité<fnref n="3" />.

La classe *Float* comporte deux méthodes de classe qui permettent de créer un nombre représenté en virgule flottante. Ce sont les méthodes :

- *fromInteger*: *anInteger* qui renvoie la représentation en virgule flottante de l'argument entier *anInteger*,

- *pi* qui renvoie la représentation approchée de π, en virgule flottante, avec la meilleure approximation permise par la machine utilisée.

Comme nous l'avons indiqué au début de 3.1, la classe *Number* rassemble des méthodes générales dont héritent toutes les sous-classes correspondant à des valeurs numériques. Pour la plupart, ces méthodes correspondent à des fonctions numériques comme, par exemple, la méthode *cos*, qui calcule le cosinus du nombre-récepteur et qui est représentée en première partie de la figure 3.4. On constate que cette méthode n'effectue pas elle-même le calcul mais envoie, à la représentation en virgule flottante du récepteur (obtenue par le message *asFloat*),

<footnote n="3">3 Smalltalk-80 implémente ces règles directement en Smalltalk et utilise, pour cela, des méthodes de conversion (« coercing messages »).</footnote>
le message `cos`. Il ne s'agit nullement d'un appel récursif, car ce dernier message `cos` est envoyé à une instance de la classe `Float` et fait donc appel à la méthode `cos` redéfinie pour cette classe et présentée en deuxième partie de la figure.

### méthode cos de la classe Number

**COS**

"Answer a Float which is the cosine of the receiver. The receiver is an angle measured in radians."

`^self asFloat cos`

### méthode cos de la classe Float

**COS**

"Answer the cosine of the receiver. The receiver is an angle measured in radians"

`<primitive: 30>`

`^self class floatError`

<caption>Figure 3.4 : les deux méthodes pour le calcul du cosinus</caption>

Examinons de plus près cette seconde méthode. L'expression `<primitive: 30>` indique qu'elle s'exécute en faisant appel à une primitive écrite en langage d'assemblage. En effet, si la majeure partie du code de Smalltalk est écrite en Smalltalk, une très faible partie de ce code est implantée sous la forme de primitifs réalisées en langage-machine<fnref n="4" />.

Dans une méthode, l'appel d'une primitive se note entre les signes `< et >`. La primitive appelée est conçue pour gérer complètement la plupart des cas du traitement auquel elle correspond. Elle ne retourne à la méthode qu'en cas d'échec. Dans ce dernier cas, le code qui suit l'appel de la méthode est évalué. Pour l'exemple de la figure 3.4, en cas d'échec de la primitive 30, la méthode `cos` renverra la valeur `self class floatError` qui appelle la méthode de classe `floatError` de la classe `Float`. Cette méthode analyse la cause de l'erreur (dépassement de capacité, division par zéro, etc) et affiche une fenêtre avec le message d'erreur correspondant.

Pour en terminer avec l'exemple de la figure 3.4, remarquons que, comme pour toutes les fonctions trigonométriques de base de Smalltalk, les angles sont évalués en radians. Pour les conversions, les méthodes `degreesToRadians` et `radiansToDegrees` sont disponibles dans la classe `Number`. Comme `cos` et la plupart des fonctions numériques, elles s'appuient sur un appel de primitive ou un calcul direct dans la classe `Float`. Nous donnons par exemple la réalisation, en Smalltalk V, des deux méthodes `degreesToRadians`.

`degreesToRadians` "méthode d'instance de Number"

"Answer the receiver converted from degrees to radians."

`^ self asFloat degreesToRadians`

`degreesToRadians` "méthode d'instance de Float"

"Answer the number of radians the receiver represents in degrees."

`^ self * Float pi / 180`

<footnote n="4">4 Une centaine de primitives pour Smalltalk V.</footnote>
A propos des conversions, examinons la manière d'obtenir, par la méthode `asFloat`, la représentation en virgule flottante d'un entier ou d'un rationnel de la classe `Fraction`. La figure 3.5 présente les méthodes utilisées. On peut être, au premier abord, surpris que la classe `Float` dispose également de cette méthode qui ne fait rien d'autre que renvoyer le récepteur. En fait cette méthode est indispensable pour assurer le bon fonctionnement des méthodes numériques héritées de `Number` qui, pour le calcul, effectuent d'abord une conversion en virgule flottante, en envoyant à l'objet récepteur le message `asFloat`, quelle que soit la classe de cet objet.

méthode asFloat de la classe Fraction
asFloat
"Answer the receiver as a floating point number."
^numerator asFloat / denominator asFloat

méthode asFloat de la classe Integer
asFloat
"Answer the floating point representation of the receiver."
^Float fromInteger: self

méthode asFloat de la classe Float
asFloat
"Answer the receiver as a floating point number."
^self

<caption>Figure 3.5 : méthodes `asFloat` pour les classes `Fraction`, `Integer` et `Float`</caption>

### 3.1.5 - Opérations arithmétiques élémentaires

Les opérations arithmétiques usuelles (addition, soustraction, multiplication, division) sont, en Smalltalk, représentées par des messages binaires pour lesquels, rappelons-le, l'ordre d'évaluation, dans une expression sans parenthèses, est de gauche à droite. Ainsi l'expression `3 + 5 * 5` s'évaluera `40` (et non pas `28`, comme avec la plupart des autres langages où la multiplication a priorité sur l'addition).

Lorsque, dans une expression, les valeurs sont de classes différentes, Smalltalk effectue les conversions nécessaires, comme le montrent les exemples de la figure 3.6. On notera, à propos du deuxième exemple de cette figure, le rôle des parenthèses qui amènent Smalltalk à évaluer d'abord $1/2$ dont le résultat est une fraction qui devient l'argument du message `+ envoyé à 1`. Sans les parenthèses l'évaluation de $1 + 1/2$ renverrait l'entier $1$. On remarquera également que les exemples de la deuxième et de la troisième ligne de la figure représentent la
même expression : sauf pour le sélecteur<fnref n="5" /> -, la présence d'espaces autour d'un opérateur est facultative.

<table><caption>Figure 3.6 : quelques expressions arithmétiques avec des valeurs de classes différentes</caption><tr><td>1 + 1.5</td><td>"renvoie 2.5 de la classe Float"</td></tr><tr><td>1 + (1/2)</td><td>"renvoie 3/2 de la classe Fraction"</td></tr><tr><td>1 + (1 / 2)</td><td>"renvoie 3/2 de la classe Fraction"</td></tr><tr><td>3/2 + 1</td><td>"renvoie 5/2 de la classe Fraction"</td></tr><tr><td>3/2 + 1.0</td><td>"renvoie 2.5 de la classe Float"</td></tr></table>

Les méthodes correspondant à toutes ces opérations arithmétiques élémentaires sont, en Smalltalk V, en général réalisées par des appels de primitives en langage-machine. Lorsque ce n'est pas le cas, la méthode utilise indirectement de telles primitives. Examinons, par exemple, la méthode * de la classe Fraction :

* aNumber
  "Answer the result of multiplying the receiver by aNumber."
  ^ (numerator * aNumber numerator)
  /
  (denominator * aNumber denominator)

Cette méthode envoie elle-même deux messages correspondant aussi au sélecteur * respectivement aux variables d'instance *numerator* et *denominator* de la fraction. Comme ces variables d'instance sont des entiers, ces deux messages déclencheront l'exécution de la méthode * de la classe *Integer* qui, elle, fera appel à une primitive en langage-machine.

Toujours à propos de la méthode * de la classe *Fraction*, on remarquera que la méthode * effectue apparemment le même traitement quelle que soit la classe de l'argument aNumber. Si cet argument est une autre fraction, on comprend bien le fonctionnement de la méthode, qui correspond à la multiplication de deux fractions. Mais, si aNumber est un entier ou une instance de la classe *Float*, on peut se demander comment il réagira aux messages *numerator* et *denominator* qui lui sont adressés, d'autant que ces deux méthodes sont apparemment absentes des deux classes *Integer* et *Float*. Elles sont, en fait, héritées de Number, où elles sont définies de la manière suivante :

numerator "méthode de la classe Number"
  "Answer the numerator of the receiver.
    Default is the receiver which can be overridden by the subclasses."
  ^ self
denominator "méthode de la classe Number"
  "Answer the denominator of the receiver.
    Default is one which can be overridden by the subclasses."
  ^ 1

Sachant maintenant que pour une instance de la classe *Float* ou *Integer*, les méthodes *numerator* et *denominator* renvoient respectivement le nombre lui-

<footnote n="5">5 Rappelons en effet (Ci 3.1.1) que seul le sélecteur – ne peut être suivi immédiatement par un chiffre car, sinon, il est interprété comme le signe d'un nombre négatif.</footnote>
même et l'entier 1, on comprend comment est assuré le polymorphisme pour la méthode * de la classe Fraction.

Pour en terminer avec les opérateurs arithmétiques, décrivons brièvement les opérateurs associés à la division. La méthode / réalise, pour toutes les classes, la division et rend le quotient avec la meilleure approximation possible, compte tenu des classes des nombres participant à la division :

12 / 4 "renvoie l'entier 3"

17 / 3 "renvoie la fraction 17/3"

17 / 3.0 "renvoie 5.666667 de la classe Float"

La méthode // permet d'obtenir l'entier immédiatement inférieur au quotient exact. Si a et b sont deux entiers positifs, a // b renvoie donc le quotient euclidien de a par b. Le reste d'une division peut être obtenu par la méthode // dont le résultat, si a et b sont des entiers relatifs, est obtenu conformément à la règle suivante :

a \ b = a - (a // b) * b.

La figure 3.7 donne quelques exemples d'utilisation de // et \.

27 // 4 "renvoie 6 car 27=4*6.75 et 6 est le plus grand entier inférieur à 6.75"

27 // -4 "-7 car 27=(-4)*(6.75) et -7 est le plus grand entier inférieur à 6.75"

<caption>Figure 3.7 : exemples d'utilisation des méthodes // et \</caption>

### 3.1.6 - Les intervalles

Bien qu'elle ne soit pas une descendante de la classe *Number*, la classe *Interval* est liée à cette première classe, dans la mesure où ses instances sont des progrèsions arithmétiques. Une instance de cette classe, est une suite croissante de nombres définie par ses trois variables d'instances :

*beginning* le premier nombre de la suite,

*end* la borne supérieure de la suite,

*increment* l'intervalle entre deux nombres consécutifs (raison de la progression).

Une instance de *Interval* peut être créée avec la méthode de classe *from/to*: ou *from/to:by*: comme le montrent les exemples de la figure 3.8. Sur cette figure, chaque nouvelle instance créée reçoit le message *asArray* et renvoie en réponse un tableau contenant tous les nombres de la progression. Le premier exemple montre que, en l'absence d'indication explicite de la raison de la progression, l'intervalle entre deux nombres consécutifs est de 1. On notera avec le troisième exemple que le dernier nombre d'une progression peut être inférieur à l'argument du mot-clé *to*: En effet, dans ce troisième exemple, la différence entre la borne supérieure et le premier nombre de la progression n'est pas un multiple de la raison de la progression. Constatons également, avec le dernier exemple de la figure, que toute suite qui ne peut être croissante, est une instance vide de la classe *Interval*.
```
(Interval from: 2 to: 5) asArray. "renvoie le tableau (2 3 4 5)"
(Interval from: 1 to: 2.5 by: 0.5) asArray.
  "renvoie le tableau (1.0 1.5 2.0 2.5)"
(Interval from: 1 to: 2.2 by: 0.5) asArray.
  "renvoie le tableau (1.0 1.5 2.0)"
(Interval from: 5 to: 2) asArray. "renvoie le tableau vide ()"
```

<caption>Figure 3.8 : quelques instances de la classe *Interval*</caption>

Une progression arithmétique, instance de *Interval*, peut aussi être créée avec les méthodes d'instances *to* et *to:by*: de la classe *Number*, qui sont héritées par toutes les sous-classes de celle-ci. Par exemple, la seconde progression de la figure 3.8 aurait pu être obtenue par l'évaluation de $1 to:2.5 by:0.5$, qui représente l'envoi d'un message à l'entier *1* qui répond en renvoyant la progression $(1.0 1.5 2.0 2.5)$.

Indiquons enfin pour terminer que la classe *Interval* est une des classes descendantes de la classe *Collection* (Cf chapitre 4) et qu'à ce titre elle dispose de la méthode *do*, déjà abordée en 2.8.3. Quand une progression arithmétique non vide, instance de la classe *Interval*, reçoit le message *do: unBloc*, elle déclenche l'évaluation de *unBloc* pour chacun des nombres qui la composent, l'argument de *unBloc* prenant successivement la valeur de chaque nombre de la progression. L'exemple suivant propose une utilisation de cette méthode pour l'affichage d'une table des carrés.

```
"affichage sur System Transcript d'une table des carrés de 1 à 20"
(1 to: 20)
do: [:entier]
    Transcript nextPutAll: 'le carré de '.
    entier printOn: Transcript.
    Transcript nextPutAll: ' est '.
    entier * entier printOn: Transcript.
    Transcript cr]
```

## 3.2 - Les tableaux

Nous avons vu dans les précédents chapitres comment créer une constante tableau (instance de la classe *Array*). Nous montrerons dans celui-ci comment manipuler les instances de cette classe en construisant un environnement facilitant le calcul matriciel. Auparavant, nous allons étudier comment créer un tableau dont les éléments ne sont plus des constantes mais les valeurs de variables.

### 3.2.1 - Variables et tableaux

Nous pouvons créer un tableau de trois manières différentes :
```
|tab1 tab2 tab3|
tab1 := Array new:3.
tab1 at:1 put:'un';
at:2 put:'deux';
at:3 put:'trois'.
tab2 := #('un' 'deux' 'trois').
```

```
tab3 := Array with: 'un' with: 'deux' with: 'trois'.
```

Les trois variables *tab1*, *tab2* et *tab3* représentent chacune un tableau contenant les objets '*un*' '*deux*' et '*trois*' . Nous avons vu dans les chapitres précédents l'instanciation par le message *new: (tab1)* et l'affectation (tab2). La dernière méthode (*with:with:with:*) est héritée de la classe *Collection* dont la descendance est présentée jusqu'à la classe *Array* dans la figure 3.9.

<figure><img src="image_14.png" /><figcaption>Figure 3.9 : position de la classe *Array* dans la hiérarchie des classes</figcaption></figure>

Ces trois façons de créer un tableau ne sont pourtant pas totalement identiques. L'évaluation par *show it* des séquences du tableau 3.10 illustre cette différence.

La première séquence évaluée renvoie un tableau contenant la lettre *x* et non la valeur 10 . La deuxième séquence renvoie la classe à laquelle appartient cette lettre *x* : il s'agit d'un symbole. La syntaxe de Smalltalk pour la classe *Array* (Cf 2.6) interdit toute utilisation d'une variable à l'intérieur des parenthèses délimitant le tableau. Dans les deux premières séquences, *x* est donc interprété par Smalltalk comme un symbole (un caractère serait précédé du signe $, une chaîne de un caractère serait encadrée par des apostrophes).

Cette syntaxe nous interdit d'intégrer ainsi la valeur d'une variable parmi les éléments d'une constante tableau : il faudra utiliser les méthodes *at:put:* ou *with:* .

La troisième séquence renvoie le nombre 20 , que nous avons chargé en dernier dans le tableau y . La méthode *at:put:* ne renvoie pas le tableau complété mais l'objet que l'on ajoute. Par contre, la méthode *with:with:* renvoie le tableau complet (quatrième séquence).
<table><caption>Tableau 3.10 : Différentes méthodes pour créer une instance de la classe Array</caption><thead><tr><th>Séquence à évaluer</th><th>Résultat</th></tr></thead><tbody><tr><td>|x|<br>x := 10.<br>#{x 20}</td><td>(x 20)</td></tr><tr><td>|x y|<br>x := 10.<br>y := #{x 20}.<br>(y at:1) class</td><td>Symbol</td></tr><tr><td>|x y|<br>x := 10.<br>y := Array new:20.<br>y at:10 put:x ;<br>at:20 put:20.</td><td>20</td></tr><tr><td>|x|<br>x := 10.<br>Array with:x with:20</td><td>(10 20)</td></tr><tr><td>|x y|<br>x := 10.<br>y := Array with:x with:x.<br>x := 20.<br>y</td><td>(10 10)</td></tr></tbody></table>

La méthode *with:with* est une méthode de classe utilisée pour créer une instance : par définition, elle doit évidemment renvoyer l'instance créée. La méthode *at:put* est une méthode d'instance, utilisée pour modifier l'objet auquel on l'applique. Dans le cas de tableaux emboîtés les uns dans les autres, les accès se font par des messages *at*: ou *at:put*: en cascade : il ne faudra pas perdre de vue que l'objet renvoyé par le message *at:put*: n'est pas le tableau lui-même.

Dans la cinquième séquence, nous voyons que la modification de la variable *x* n'a aucune incidence sur le contenu du tableau *y*, qui a pourtant été défini avec cette variable *x*. Une variable n'est pas un objet : elle n'est qu'un pointeur sur un objet (Cf 2.6). En évaluant la ligne *y := Array with:x with:x.*, Smalltalk n'associe pas les éléments du tableau à la variable *x* mais recopie le pointeur sur l'objet *10* (contenu actuel de la variable *x*) dans chacune des deux cases du tableau. Modifier la variable *x* (donc changer la valeur du pointeur) ne peut avoir d'incidence sur le contenu de *y*, puisque les cases du tableau pointent toujours sur l'objet *10* qui lui, reste intact. Nous voyons là une nouvelle illustration de la philosophie « orientée objets » : l'objet *y* est seul responsable de son intégrité.

Ces différents points peuvent surprendre un programmeur habitué aux langages structurés. Ils imposent en tout cas de porter une attention particulière à la
définition des méthodes construisant des tableaux emboîtés les uns dans les autres, comme nous allons le voir dans le paragraphe suivant.

### 3.2.2 - Un exemple d'utilisation des tableaux : le calcul matriciel

Le calcul matriciel s'effectue sur des matrices. Cette remarque de bon sens nous conduit à une première interrogation : existe-t-il une classe dont les instances pourraient être des matrices ?

La réponse est oui : la classe Array permet de créer un tableau dont les éléments peuvent être à leur tour d'autres tableaux<fnref n="6" />. Nous sentons pourtant que la manipulation de telles instances risque d'être lourde et relativement complexe.

Il est préférable de créer une classe héritant de Array. Cette classe utilisera les méthodes de ses classes mères, mais nous pourrons lui ajouter quelques méthodes spécifiques, comme *discriminant* ou *.

Une nouvelle question nous arrête : faut-il créer une classe par type de matrice (2x2, 2x3 etc...) ou regrouper les matrices de toutes dimensions au sein de la même classe ?

Avoir une seule classe offre évidemment plus de souplesse. Mais les méthodes seront plus complexes à établir. D'autre part, il sera nécessaire de connaître les dimensions de chaque instance, pour pouvoir appliquer les calculs à bon espace. Nous définirons donc deux variables d'instance (*ligne* et *colonne*) donnant les grandeurs horizontales et verticales de la matrice.

#### Créer une nouvelle classe

Pour créer la nouvelle classe *Matrice*, nous allons travailler sur la fenêtre du *Class Hierarchy Browser* (Cf 1.7). Il faut d'abord cliquer sur le nom de la classe mère, *Array*, dans le panneau de gauche (panneau des classes) en faisant défiler si nécessaire la liste des classes avec la souris. Une fois la classe *Array* sélectionnée, une courte pression du bouton droit de la souris provoque l'affichage d'un menu. Cliquer sur l'option *add subclass* permet d'ajouter une nouvelle classe. Après avoir entré son nom, il faut préciser son type (dans notre cas, nous sélectionnerons le type *variableSubclass*<fnref n="7" />). Le panneau inférieur affiche alors la définition provisoire de notre classe (figure 3.11).

```
Array variableSubclass: #Matrice
instanceVariableNames: ''
classVariableNames: ''
poolDictionaries: ''
```

<caption>Figure 3.11 : définition provisoire de la classe *Matrice*</caption>

<footnote n="6">6 Nous pouvons assimiler une matrice à un tableau de colonnes (ou de lignes).</footnote>

<footnote n="7">7 Nous verrons au paragraphe 3.3 la signification des différents types de sous-classes.</footnote>
Ce panneau fonctionne comme une fenêtre d'édition de texte. Nous allons modifier la ligne

instanceVariableNames: ''

en ajoutant deux variables d'instance entre les parenthèses :

instanceVariableNames: 'ligne colonne'.

Pour enregistrer les modifications, il faut encore appeler le menu de ce panneau (en cliquant rapidement avec le bouton droit de la souris) et choisir l'option save.

## Accéder aux variables d'instance

La plupart des méthodes que nous allons construire pour faire du calcul matriciel devrait connaître les dimensions des matrices. Nous définirons donc quatre méthodes d'instance permettant de définir et de consulter les variables *ligne* et *colonne* (figure 3.12).

Les variables d'instance *ligne* et *colonne* prendront définitivement leurs valeurs à la création de la matrice. Les méthodes *ligne*: et *colonne*: seront donc appelées par la méthode de classe chargée de créer de nouvelles instances. Les méthodes *ligne* et *colonne* seront utilisées par toutes les méthodes d'instance nécessitant les dimensions de la matrice pour fonctionner.

ligne
"Répond la valeur de ligne."
^ligne
ligne: uneValeur
"Fixe la valeur de ligne."
ligne := uneValeur
colonne
"Répond la valeur de colonne."
^colonne
colonne: uneValeur
"Fixe la valeur de colonne."
colonne := uneValeur

<caption>Figure 3.12 : méthodes d'accès aux variables d'instance</caption>

## Créer une nouvelle matrice

Une matrice étant une succession de lignes, nous pouvons l'assimiler à un tableau de tableaux. Créer une nouvelle instance revient alors à créer un tableau contenant autant de positions que de lignes dans la matrice (méthode de classe *deDimensions*:), puis à mettre à chaque position un tableau dont le nombre
d'éléments sera le nombre de colonnes de la matrice (méthode d'instance `ligne:colonne:`). La méthode `deDimensions:` (figure 3.13) crée cet objet.

<figure><img src="image_15.png" /><figcaption>Figure 3.13 : méthode de classe `deDimensions`:</figcaption></figure>

La méthode `deDimensions:` telle que nous la décrivons figure 3.13 utilise un argument : celui-ci est un tableau contenant les deux dimensions de la matrice. Une matrice 2x3 sera créée en évaluant `Matrice deDimensions:#(2 3)`.

La première évaluation de la méthode `deDimensions:` consiste à créer un tableau de la classe `Matrice` qui contiendra autant d'éléments que de lignes dans la matrice (`new:(unTableau at:1)`). Le nombre de lignes de la matrice est extrait du tableau donné en argument par l'expression (`unTableau at:1`). Cette nouvelle instance reçoit ensuite le message `ligne:colonne:`.

<figure><img src="image_16.png" /><figcaption>Figure 3.14 : méthode d'instance `ligne:colonne`:</figcaption></figure>

La méthode `ligne:colonne:` (figure 3.14) fixe d'abord les valeurs des variables d'instance `ligne` et `colonne` puis affecte à chaque élément du tableau receveur un tableau vide de taille `colonne`. La matrice est prête à recevoir ses valeurs numériques.

## Charger les valeurs numériques dans une matrice

Nous pouvons charger les valeurs d'une matrice élément par élément. Cette opération risque d'être fastidieuse. Nous pouvons aussi les charger ligne par ligne (ou colonne par colonne). Enfin, nous pouvons transférer le contenu d'un tableau dans une matrice, pour peu que nous respections des conventions préétablies.

Dans tous les cas, nous aurons besoin d'accéder à un élément précis de la matrice, défini par ses coordonnées en ligne et colonne.

La méthode `auxCoordonnees:` décrite figure 3.15 effectue un accès en deux étapes pour accéder à l'élément dont les coordonnées sont précisées dans l'argument `unTableau`. Le premier appel de `at:` renvoie la ligne en position (`unTableau at:1`); l'appel suivant extrait l'élément de position (`unTableau at:2`) dans cette ligne.
<figure><img src="image_17.png" /><figcaption>Figure 3.15 : méthode `auxCoordonnees:` pour accéder à un élément de la matrice</figcaption></figure>

La méthode symétrique `auxCoordonnees:` unTableau met: uneValeur (figure 3.16) remplace la valeur de position unTableau par uneValeur.

<figure><img src="image_18.png" /><figcaption>Figure 3.16 : méthode `a:met`:</figcaption></figure>

La modification est réalisée dans le tableau de la ligne considérée (ligne de numéro (unTableau at:1)) par la méthode `at:put:`.

Entrer les éléments de la matrice un par un n'est pas pratique. La méthode `prendLesValeurs:` tableValeurs (figure 3.17) charge en une seule fois toutes les valeurs de la matrice stockée dans tableValeurs, instance de Array.

<figure><img src="image_19.png" /><figcaption>Figure 3.17 : méthode `prendLesValeurs`:</figcaption></figure>

La méthode `prendLesValeurs:` tableValeurs (figure 3.17) nous permet d'introduire une nouvelle méthode d'instance de Array: `copyFrom:` firstIndex to: lastIndex. Comme son nom l'indique, cette méthode renvoie une copie du récepteur comprise entre ses éléments de rang firstIndex et lastIndex. Les variables d'instance ligne et colonne sont utilisées dans `prendLesValeurs` pour
découper en *ligne* tableaux de *colonne* éléments la liste de valeurs *tableValeurs*. Pour créer une matrice, nous pouvons maintenant évaluer :

|x|
x := Matrice nouveau:#{4 2}.
x prendLesValeurs:#{1 0 2 0 1 0 1 0}.

Le résultat affiché à l'écran après évaluation avec l'option *show it* est :

Matrice((1 0)(2 0)(1 0)(1 0))

La représentation de la matrice à l'écran peut être améliorée en redéfinissant la méthode `printOn`: dans la classe `Matrice`. La méthode `printOn`: est appelée lors de l'évaluation d'une séquence par *show it*. Son rôle est d'afficher le contenu du récepteur (en l'occurrence notre matrice) dans un éditeur de texte (la fenêtre de travail)<fnref n="8" />. Nous proposons dans la figure 3.18 une redéfinition possible de cette méthode qui permet d'obtenir la matrice précédente sous la forme :

(1 0
2 0
1 0
1 0)

La méthode `printOn`: faisant appel aux flux, son contenu ne sera pas étudié dans le cadre de ce chapitre.

```
printOn:aStream
    "Imprime les valeurs de la matrice présentées sous forme habituelle"
    aStream cr.
    aStream nextPut:$(
        1 to:ligne do:[:lig]
        1 to: colonne do:[:col]
        (self auxCoordonnees:(Array with:lig
                                     with:col))
        printOn:aStream.
        "printOn: est adressé à l'entier de position (lig col)
           dans la matrice réceptrice. Il est donc fait appel à la
           méthode printOn: définie dans la classe Integer"
        aStream tab].
        (lig = ligne) ifTrue:[aStream nextPut:$]).
        aStream cr
    ].
    ^self
```

<caption>Figure 3.18 : amélioration de l'affichage des matrices à l'écran</caption>

<footnote n="8">8 La méthode `printOn`: envoie une représentation imprimable du récepteur dans un flux, qui peut être récupérée par une fenêtre d'éditions de texte. Cette méthode sera abordée dans le chapitre 5, consacré aux flux.</footnote>
## Multiplier deux matrices

Nous pouvons maintenant construire une méthode permettant de multiplier deux matrices (figure 3.19).

### * uneMatrice

"renvoie le produit de la matrice réceptrice par la matrice argument, en vérifiant que leurs dimensions sont compatibles"

| resultat somme |
(self colonne = uneMatrice ligne)

ifFalse:[^self error:'Impossible de multiplier deux matrices de ces dimensions'].

"la matrice résultante sera stockée dans resultat, qui est donc créée aux bonnes dimensions"

resultat := Matrice deDimensions:
  (Array with:self ligne
    with:uneMatrice colonne).

1 to:self ligne do:[:i]

"i est l'indice des lignes de la matrice de gauche"

1 to:uneMatrice colonne do:[:j]

"j est l'indice des colonnes de la matrice de droite"

somme := 0.

"somme stocke temporairement la valeur à mettre dans la matrice résultat"

1 to:self colonne do:[:k]

"k est l'indice de la valeur à multiplier"

somme := somme +

((self auxCoordonnees:
  (Array with:i with:k)))

* (uneMatrice auxCoordonnees:
  (Array with:k with:j))))].

"fin du calcul de la valeur à charger. Il reste à placer cette valeur dans la matrice résultat"

resultat auxCoordonnees:(Array with:i with:j)
  met:somme]

"fin d'une ligne de la matrice résultat"

}

"fin de toutes les lignes de la matrice résultat"

^resultat

<caption>Figure 3.19 : multiplier deux matrices</caption>

Cette méthode est particulièrement démonstrative des difficultés que le débutant peut rencontrer lorsqu'il doit donner un tableau en argument d'une méthode. La matrice *resultat* est créée par l'envoi du message *deDimensions:unTableau* à la classe *Matrice* (*resultat* := *Matrice deDimensions: (Array with: self ligne with: uneMatrice colonne)*). Dans notre cas, nous ne connaissons pas à l'avance la taille de la nouvelle matrice à créer. *unTableau* contient donc deux variables (*self ligne* et *uneMatrice colonne*). Mais nous avons
vu qu'il est impossible de créer directement une instance de la classe Array, comme nous serions tentés de le faire avec :

```Smalltalk
#(self ligne uneMatrice colonne)
```

L'évaluation de cette ligne renverra un tableau contenant quatre symboles (self, ligne, uneMatrice et colonne)<fnref n="9" />. Il faut donc passer par la classe Array, lui envoyer un message (with:with:) qui acceptera en paramètre le résultat des messages self ligne et uneMatrice colonne.

Dans ce cas précis, l'objet paramètre unTableau n'a que deux éléments. Nous pouvons donc utiliser directement la méthode with:with: pour créer et affecter des valeurs à un tableau en une seule opération. Mais la classe Collection (et Array par voie de conséquence) ne dispose que de quatre méthodes de ce type (with:, with:with:, with:with:with:, with:with:with:with:). Pour des tableaux ayant plus de quatre éléments, il faudra couper l'opération en :

```Smalltalk
unTableau := Array new: 5.
unTableau at:1 put:objet1;
at:2 put:objet2;
at:3 put:objet3;
at:4 put:objet4;
at:5 put:objet5.
```

La méthode * fait aussi appel à auxCoordonnees: et auxCoordonnees:met: qui prennent en paramètre un tableau dont les éléments sont les variables locales i, j et k. Nous utilisons de nouveau la séquence Array with:i with:j pour obtenir un tableau ayant comme éléments les objets pointés par les variables i et j.

Pour vérifier le fonctionnement de la méthode, évaluons avec l'option show it :

```
|x y|
x := Matrice deDimensions: #(4 2).
x prendLesValeurs: #(1 0 2 0 1 0 1 0)
y := Matrice deDimensions: #(2 3).
y prendLesValeurs: #(1 0 0 0 1 0).
x * y
```

Smalltalk renverra :

```
(1 0 0
2 0 0
1 0 0
1 0 0)
```

## 3.3 - Les chaînes de caractères

Dans la hiérarchie des classes, String est située au même niveau que la classe Array (figure 3.20) et hérite des mêmes classes que celle-ci.

<footnote n="9">9 Smalltalk considère que le contenu des parenthèses suivant le signe # est la représentation exacte du tableau. A part une vérification syntaxique, il ne lance aucune évaluation de l'expression comprise entre les parenthèses.</footnote>
<figure><img src="image_20.png" /><figcaption>Figure 3.20 : la classe *String* dans la hiérarchie des classes</figcaption></figure>

Nous avons vu dans les chapitres précédents quelques exemples de chaînes de
caractères. Nous allons présenter dans ce chapitre de nouvelles méthodes utili-
sables dans la classe *String*. Nous nous servirons ensuite de cette classe pour ap-
prendre à créer, modifier, détruire ou récupérer une sous-classe et ses méthodes
associées.

3.3.1 - Quelques manipulations de chaînes de caractères

Traiter des informations dans une chaîne de caractères<fnref n="10" />

Outre les méthodes héritées de ses classes mères (que nous verrons au chapitre
4), *String* permet de travailler sur les chaînes pour en extraire un tableau de
mots<fnref n="11" /> (*asASFOfSubstrings*), un nom de fichier *MS-DOS* (*fileName*) ou le suffixe
d'un tel nom (*fileExtension*).

L'évaluation par *show it de* :

<table>
  <tr>
    <td>('nomfic ext 23456 23/12/90 12:00:00'</td>
    <td>asASFOfSubstrings</td>
    <td>"transforme la chaîne en tableaux de sous-chaînes"</td>
  </tr>
  <tr>
    <td></td>
    <td>at:1)</td>
    <td>"renvoie le premier élément du tableau, donc la première sous-chaîne"</td>
  </tr>
  <tr>
    <td></td>
    <td>fileName</td>
    <td>"renvoie le champ correspondant au nom du fichier MS-DOS"</td>
  </tr>
  <tr>
    <td></td>
    <td>asUppercase.</td>
    <td>"renvoie le nom du fichier en majuscules"</td>
  </tr>
</table>

extrait, d'une ligne analogue à celle que l'on pourrait obtenir avec la commande
MS-DOS *dir*, le nom d'un fichier converti en majuscules (*NOMFIC*).

<footnote n="10">10 L'exemple sera tiré du système d'exploitation MS-DOS et de Smalltalk V.</footnote>

<footnote n="11">11 Ce ne sont pas des mots au sens linguistique du terme, mais une suite de caractères compris entre deux espaces de la chaîne d'origine.</footnote>
A partir de ces méthodes, l'utilisateur peut se construire un environnement personnalisé, capable de traiter les chaînes de caractères de ses fichiers texte (recherche et remplacement de mots...), tableur, base de données etc.

## Améliorer la présentation

**withCrs** remplace dans une chaîne toutes les occurrences du caractère \ par un code lf (retour de ligne). Cette méthode est intéressante car elle permet de noter sur une seule ligne une suite de mots à disposer en colonne. Toutes les méthodes d'affichage des menus l'utilisent.

Ainsi, `Remove\New\Create\Exit` withCrs présentera la chaîne de la manière suivante :

Remove
New
Create
Exit

**trim: aCharacter** renvoie la chaîne réceptrice élaguée des caractères dont le code ASCII est inférieur ou égal à celui de *aCharacter*. La suppression s'effectue à la fois au début et à la fin de la chaîne et s'interrompt au premier caractère dont le code est strictement supérieur à celui de *aCharacter*.

Ainsi, `PLANTEZ CE CLOU A DEUX METRES`, ('DE LA BASE' trim:$E)

renvoie 'PLANTEZ CE CLOU A DEUX METRES LA BAS', ce qui n'est pas la même chose<fnref n="12" />. Nous voyons que les deux lettres A (dont le code est inférieur à celui de $E) de LA BAS ont été maintenues dans la chaîne renvoyée après évaluation du message trim:$E. La suppression des caractères s'est interrompue au L en début de chaîne et au S en fin de chaîne.

**trimBlanks** supprime les blancs de début et de fin.

**edit** crée une fenêtre éditeur de texte sur l'écran et y affiche la chaîne.

**outputToPrinter** imprime la chaîne sur l'imprimante.

**printOn:aStream** charge la chaîne dans le flux aStream (voir le chapitre 5).

Et, comme curiosité, la figure 3.21 illustre quelques possibilités d'affichage supplémentaires.

<footnote n="12">12 Nous verrons au chapitre 4 les méthodes de la classe Collection. Le sélecteur, (virgule) correspond à une de ces méthodes. Il permet d'accoler la chaîne donnée en paramètre à la chaîne réceptrice pour en faire une chaîne unique.</footnote>
<figure><img src="image_21.png" /><figcaption>Figure 3.21 : affichage d'une chaîne à l'écran</figcaption></figure>

### 3.3.2 - Problèmes d'héritage liés à la classe String

La notion d'héritage incite le programmeur à créer une nouvelle sous-classe lorsqu'il désire modifier une méthode d'une classe donnée. En effet, les classes sont des objets partagés par tous les utilisateurs. Il est pratiquement certain que la modification d'une classe existante, même mineure, aura des conséquences imprévisibles sur le déroulement d'autres méthodes. L'héritage permet alors au programmeur de protéger l'environnement commun, tout en créant des objets qui bénéficieront de cet environnement.

Pourtant, le respect de cette règle risque de donner bien des soucis comme nous allons l'illustrer par un exemple tiré de la classe *String*.

Supposons que nous voulions manipuler des mots de la langue française. Toutes les opérations de comparaison nécessiteront probablement le passage en majuscules (pour se libérer des choix typographiques propres à chaque opérateur) : mais nous avons vu précédemment (Cf 1.6) que la fonction *asUppercase* proposée dans Smalltalk V ne sait traiter qu'un alphabet anglo-saxon. Elle ignore le traitement particulier des caractères accentués dans la langue française. Nous pouvons le vérifier une nouvelle fois avec :

'Huîtres à Noël, oeufs à pâques et pâture en été'
asUppercase

qui affiche après évaluation par *show it* :
HUÎTRES à NOËL, OEUFS à PÂQUES ET PÂTURE EN ÉTÉ

Nous allons donc ajouter une sous-classe à la classe *String*: *MotFrancais* par exemple<fnref n="13" /> (option *variableByteSubclass*<fnref n="14" />) dans laquelle nous ajouterons la version française de *asUppercase*.

Dans *String* (et donc dans *MotFrancais* qui en hérite), les méthodes de classe permettant de créer un objet sont peu pratiques. Héritées de la classe *collection*, elles limitent la taille des mots à quatre lettres (méthodes de classe *with*: à *with:with:with:with:*) ou imposent une écriture rebutante au programmeur (méthode de classe *new*: qui nécessite de connaître au moins approximativement la taille de la chaîne à créer, suivie d'autant de messages *at: position put: lettre qu'il y a de caractères dans la chaîne*).

En réalité, dès qu'il s'agit d'affecter un objet chaîne à une variable, le moyen le plus immédiat est d'utiliser une constante :

|chaîne|

chaîne ::= 'Les sanglots longs des violons de l''automne'.

Smalltalk associe automatiquement la variable *chaîne* à un nouvel objet de la classe *String* de valeur *Les sanglots longs des violons de l'automne* (de la même façon, il interprétera automatiquement un nombre comme une instance de la classe *Integer*).

Mais comment créer simplement une instance de *MotFrancais* ? Employer le symbole d'affectation de Smalltalk ne donnera qu'une instance de *String* :

|mot|

mot ::= MotFrancais new:12.

mot ::= 'mot français'.

mot class

ne renverra jamais *MotFrancais* mais *String*, car la deuxième affectation interprétée par Smalltalk associe obligatoirement le contenu des apostrophes à la classe *String*.

Les méthodes de classe héritées des classes mères de *String* (voir chapitre 4) sont d'un emploi trop fastidieux. Il est donc nécessaire de disposer d'une méthode de classe qui puisse récupérer une chaîne existante (de la classe *String*) pour la dupliquer dans une instance de *MotFrancais*. Nous allons créer la méthode de classe *aPartirDe*: dans *MotFrancais* (figure 3.22).

<footnote n="13">13 Notre souci de préserver la pureté de la langue française aurait dû nous inciter à écrire cette nouvelle classe *MotFrancais* et non *MotFrancais*. Malheureusement, l'origine anglo-saxonne de Smalltalk V limite la compréhension de son analyseur syntaxique aux seuls caractères anglo-saxons. L'évaluation de x := *MotFrancais* new:12 s'interrompraient sur ç en signalant 'should be selector'.</footnote>

<footnote n="14">14 Nous verrons la signification de cette option au paragraphe suivant (3.3.4), consacré aux manipulations de classes.</footnote>
```Smalltalk
aPartirDe: uneChaine
    "crée une nouvelle instance de MotFrancais à partir de uneChaine "
|phrase|
    phrase := MotFrancais new: uneChaine size.
1 to: (uneChaine size)
    do[:indice| phrase at:indice
        put:(uneChaine at:indice)].
    ^phrase
```

<caption>Figure 3.22 : la première méthode de MotFrancais</caption>

L'évaluation avec l'option *show it* de l'expression :

(MotFrancais aPartirDe: 'Noël à Pâques') class

donne bien *MotFrancais*. Notre conversion a réussi. Le programmeur pourrait maintenant construire les méthodes particulières au traitement des mots français (méthode *enMajuscules* etc.).

La seconde difficulté que rencontre l'utilisateur lorsqu'il crée une sous-classe de *String* réside dans la manière particulière dont est redéfinie la méthode = pour la classe *IndexedCollection* et ses sous-classes (voir chapitre 4). Non seulement le contenu de la collection est comparé objet par objet, mais il est nécessaire que les deux instances appartiennent à la même classe. Autrement dit :

(MotFrancais aPartirDe: 'identique') = 'identique'
rendra *false*.

Il faut donc redéfinir la méthode = dans la classe *MotFrancais* de manière à ce qu'elle se contente de comparer les caractères composant les deux chaînes, sans faire intervenir leurs classes (figure 3.23).

Malgré cette précaution, l'évaluation avec *show it* de :

```Smalltalk
|mot word|
word := 'waterloo'.
mot := MotFrancais aPartirDe 'waterloo'.
word = mot
```

ne donnera ni 'austerlitz' ni le mot de Cambronne mais *false*.

L'expression `word = mot` représente l'envoi du message = mot à l'objet `word`, instance de `String`. C'est donc la méthode = de `String` qui est utilisée, où l'appartenance à une même classe est une condition nécessaire pour renvoyer `true`.
```Smalltalk
= unMot
"renvoie true si le récepteur et unMot sont composés des mêmes caractères,
false sinon, indépendamment de la classe mère du récepteur et de unMot"
| taille position |
"taille va stocker la longueur de la chaîne, position la position courante du caractère
examiné"
((taille:=self size)~:= unMot size) ifTrue:[^false].
"si les chaînes n'ont pas la même taille, alors elles sont différentes"
position := 1."on se place au premier caractère"
"tant qu'il y a des caractères dans la chaîne:"
[position <= taille] whileTrue:[
((self at:position) = (unMot at:position))
ifFalse:[^false] "au moins un caractère est différent"
ifTrue:[position:=position+1] "le dernier caractère étudié est
identique, on avance d'une
position").
^true "tous les caractères sont identiques, les deux chaînes le sont donc aussi"
```

<caption>Figure 3.23 : redéfinition de la méthode = dans la classe MotFrancais</caption>

Nous pourrions bien sûr modifier à son tour la méthode = dans la classe String. Ce serait contraire à notre souci d'intégrité des classes d'origine. Pour ces opérations de comparaison, une méthode de conversion renvoyant une instance de String à partir d'une instance de MotFrancais peut être utile (figure 3.24).

```Smalltalk
asString "méthode d'instance de MotFrancais"
"renvoie la chaîne de caractères composant le récepteur dans une instance de String"
| chaîne |
chaîne := String new: self size.
1 to: (self size) do:[:indice|
    chaîne at:indice
        put:(self at:indice)].
^chaîne
```

<caption>Figure 3.24 : traduire une instance de MotFrancais en instance de String</caption>

```Smalltalk
|mot word|
word := 'waterloo'.
mot := Motfrancais aPartirDe 'waterloo'.
word = mot asString
```

renvoie maintenant true.

L'examen d'une fenêtre d'édition des classes (Class Hierarchy Browser) montre d'ailleurs que de nombreuses méthodes de conversion sont disponibles dans la classe String, qui permettent de traduire une chaîne en entier, en majuscule, en minuscule, en symbole, en date, en heure, en flux (classe Stream étudiée au chapter 5). Nous y trouvons même la méthode asString : elle n'est toutefois pas utilisée.
sable pour traduire les instances de *MotFrancais*, puisque son seul effet est de renvoyer le récepteur tel quel.

D'une manière générale, le programmeur doit rester attentif à la nature des objets qu'il manipule. La construction de méthodes comparant des chaînes de caractères nécessite la plupart du temps l'appel à des méthodes de conversion (en matjuscules, en symbole, en chaîne).

Il faut donc que ces méthodes de conversion soient présentes dans toutes les classes contenant les chaînes comparées. C'est ce que nous avons fait pour *MotFrancais* en lui ajoutant la méthode *asString* (les autres sont héritées de *String*). Le polymorphisme de la méthode *asString* impose d'ailleurs qu'elle soit définie aussi dans la classe *String*. L'expression *objet asString* est toujours comprise et son évaluation renvoie systématiquement une instance de *String*, et ce, qu'*objet* soit une instance de *String* ou non.

### 3.3.3 - Les arguments d'une méthode : variables ou objets ?

Nous avons abordé au chapitre 2 les notions élémentaires de la programmation orientée objets et leur implémentation dans Smalltalk. Nous avons vu la distinction à faire entre un objet et une variable. Ces conceptions surprennent parfois le programmeur formé aux langages structurés. Nous allons revenir dans ce paragraphe sur quelques difficultés qu'il peut rencontrer.

Un programme Smalltalk débute en général par une séquence d'instructions comparable à celle-ci :

```
|v|
v := o
```

Nous avons signalé, au début de cet ouvrage, l'abus de langage que nous commettions en parlant de l'objet *v* alors que *v* n'était qu'une variable désignant un objet *o*. La plupart du temps, ce manque de rigueur ne prête pas à conséquence. Les exercices qui suivent montrent que ce n'est pas toujours le cas.

#### Une variable peut désigner des objets différents dans une même séquence

Nous avons vu que la séquence suivante est acceptée par Smalltalk :

```
|mot|
mot := MotFrancais new:12.
mot := 'mot français'
```

*mot* étant une variable, elle peut recevoir successivement plusieurs valeurs. Ces valeurs sont des objets dont l'existence est liée à la durée de vie de la variable qui les pointe (l'instance de *MotFrancais*, vide de caractères, sera détruite lorsque la variable *mot* désignera '*mot français*' ; l'instance de *String* '*mot français*' sera supprimée une fois la variable *mot* disparue de l'environnement, en fin d'évaluation). L'instance de *MotFrancais* ne change pas de classe pour appartenir à *String* à la suite de la deuxième affectation : elle est détruite.

De même, la séquence suivante est tout à fait valide :
```
|mot|
mot := 'Le jour '.
mot := mot,'le plus long'
```

Pourtant, le signe `mot` n'est pas interprété exactement de la même façon par Smalltalk selon qu'il est à droite ou à gauche du signe d'affectation. Dans la dernière expression, c'est l'objet désigné par la variable `mot` (à droite du signe `:=`) qui reçoit le message binaire `'le plus long'` alors que c'est à la variable `mot` (à gauche du signe `:=`) que l'on affecte le résultat de ce message binaire.

## Une variable transmise en argument d'une méthode est considérée comme un objet

Nous avons présenté au paragraphe 3.3.1 la méthode `printOn`: dont le rôle est d'ajouter le récepteur (en l'occurrence une chaîne de caractères) au flux donné en argument.

Essayons de construire dans la classe `MotFrancais` une méthode comparable qui ajoutera le récepteur à un texte (instance de `String`) donné en argument. La figure 3.25 présente le corps de cette méthode (`ajouteA:`) qu'un programmeur expérimenté en langages classiques pourrait être tenté de construire.

Nous espérons ainsi pouvoir évaluer une séquence semblable à :

```
| x y |
x := MotFrancais aPartirDe:'peau d'âne'.
y := 'Mots dont l'orthographe est modifiée : '
x ajouteA:y.
```

`x` et `y` sont deux variables auxquelles nous affectons temporairement la valeur de deux objets (instances de la classe `MotFrancais` et de la classe `String`). Nous les assimilons à des objets.

```
ajouteA:unTexte
  "ajoute le contenu du récepteur à la fin de unTexte"
unTexte := unTexte,self
```

<caption>Figure 3.25 : exemple de méthode illégale</caption>

Il nous est impossible de sauvegarder cette nouvelle méthode : l'analyse effectuée par Smalltalk s'interrompt et un message d'erreur nous signale que l'affectation `unTexte :=` est impossible dans ce contexte.

La méthode `ajouteA:` réalise pourtant un travail qui ne pose aucun problème si nous l'effectuons directement dans une fenêtre de travail :

```
| x y |
x := MotFrancais aPartirDe:'peau d'âne'.
y := 'Mots dont l'orthographe est modifiée : '
y := y,x.
y
```

L'évaluation de cette séquence par l'option *show it* montre que la variable `y` contient maintenant le texte `'Mots dont l'orthographe est modifiée : peau d'âne'`.
L'échec de la méthode *ajouteA*: vient de ce que Smalltalk lui-même considère les variables x et y comme des objets (dont il doit respecter l'intégrité) à certains moments :

- y, paramètre donnée du message *ajouteA:y*, ne représente plus la variable y (donc le pointeur sur l'objet désigné par y) mais l'objet lui même dès que Smalltalk évalue la séquence de messages contenue dans la méthode *ajouteA*:

- l'analyse effectuée par Smalltalk rejette donc toute affectation susceptible de modifier l'intégrité des objets manipulés dans la méthode.

- de la même manière, l'expression `self :=` serait refusée.

Nous verrons plus loin qu'il existe pourtant des méthodes (qui font appel à des primitives systèmes) qui permettent de forcer la réaffectation d'un objet<fnref n="15" />.

En langage orienté « objets », l'objet est strictement responsable de son intégrité : l'objet argument d'un message ne peut être modifié sans « donner son accord ». Nous pouvons vérifier que la modification de l'argument (*aStream*) est réalisée par l'envoi d'un message à cet argument (*aStream nextPut: character*). En définitive, c'est bien l'objet lui-même qui s'est modifié.

## 3.4 - Créer une classe, sauvegarder et récupérer ses méthodes

### 3.4.1 - Quel type de classe créer ?

Lorsque nous avons créé *MotFrancais*, sous-classe de *String*, en utilisant les menus d'une fenêtre d'édition des classes (*Class Hierarchy Browser*), il nous a fallu préciser quel type de sous-classes nous voulions définir (Cf 3.3.2).

Nous avons remarqué la grande variabilité des objets gérés par Smalltalk. Certaines classes, comme *Character* ou *Integer* ont des instances de taille fixe et prévisible. D'autres, comme *String* ou *byteArray* ont des instances de taille fixe mais différentes entre elles. Certaines, comme *Bag*, ont des instances de taille variable.

Il est donc utile de savoir quel type d'instance une classe va contenir, afin de préparer au mieux la mémoire de la machine à la création d'objets de cette classe.

La stratégie d'utilisation de la mémoire prend quatre formes sous Smalltalk :

- les objets sont prédéfinis, et il n'y a pas lieu de créer de nouvelles instances de la classe : c'est le cas de *Character* ;

- les objets sont considérés comme prédéfinis, mais n'apparaissent réellement en mémoire que si la nécessité l'impose : c'est le cas des sous-classes de la classe *Integer*, pour laquelle la création d'instance est impossible<fnref n="16" /> ;

<footnote n="15">15 C'est le cas de la méthode *become*; qui sera abordée en 4.9.3.</footnote>

<footnote n="16">16 Leur utilisation est bien entendu possible. Lorsque le programmeur fait appel à un objet de cette classe (comme dans l'expression `|x| x:=12345. y:=x*x`), l'instance est créée en mémoire (dans notre exemple, la variable x pointera sur un objet de la classe *Integer* et de valeur 12345 qui n'existait pas réellement en mémoire avant l'évaluation de l'expression).</footnote>