). Et
pourtant, en interne, + a déja plusieurs
significations, comme dans 2+4 et 'a'+'nimal'.
1.4 La gestion modulaire
Cette gestion par fichier inclus, modules
pré-compilés (unités) est lourde
et inadaptée à un développement
modulaire : charger toute une unité pour une
seule procédure (crt pour clrscr par exemple)
est lourd. Quand on extrait un module d'un fichier
inclus et qu'on le modifie pour un nouveau programme,
on se retrouve avec deux versions ressemblantes d'un
même module. Le réinjecter dans le
fichier originel après modification obligerait
à revérifier tous les programmes
utilisant ce fichier...
1.5 Les expressions régulières
On peut écrire if c in ['o','n'] mais pas if c ~
[A-Z][A-Z0-9]* qui signifierait si c correspond
à une lettre suivie d'un nombre quelconque de
lettres ou chiffres.
1.6 Les tableaux associatifs (ou l'indexation
généralisée)
Par exemple T["oui"] ne peut être utilisé.
Pourtant, ce genre d'indexation simplifie
énormément les recherches, les comptages.
2. Vers le Turbo Pascal Objet
2.1 Exemple de la surcharge
Nous nous limiterons dans un premier temps à la
notion de surcharge des noms de modules
(procédures ou fonctions). Prenons comme
exemple celui de la lecture et de l'écriture de
trois nombres : nbe (un entier), nbr (un réel)
et nbf (une fraction). Un exemple plus concret
pourrait être celui de la lecture et de
l'écriture de trois nombres : k (un nombre de
kilos), p (un prix), n (un numéro de facture)
mais pour des raisons exposées plus loin nous
préférons nbe, nbr et nbf. Prenons un
algorithme simple de demande de nbe et nbr :
{ Demande de nbe et nbr }
écrire " donner un entier "
lire nbe
écrire " et aussi un réeel "
lire nbr.
Le typage des variables nbe et nbr est (humainement)
explicite d'après le contexte et les messages de
demande. Si ce n'avait pas été le cas,
fidèles à nos principes, des indications
de typage auraient été mises en
commentaires. Il est facile d'écrire un
programme Pascal traditionnel qui l'exécute.
Par contre, si on rajoute la demande de nbf, on tombe
sur le problème de la lecture des deux
éléments de nbf, à savoir son
numérateur et son dénominateur. Aucun
lecteur n'a de mal à comprendre l'algorithme
{ Demande de nbe, nbr et nbf }
écrire " donner un entier "
lire nbe
écrire " donner aussi un réeel "
lire nbr
écrire " et enfin une fraction "
lire nbf.
La traduction en Pascal classique est délicate.
Après l'introduction du type fraction, soit on
utilise une procédure LitFraction soit on
écrit directement la lecture du
numérateur et du dénominateur. Les deux
programmes correspondants ne sont donc pas
homogènes : soit readln n'apparait plus
explicitement pour lire nbf, soit il apparait deux fois
(une pour chaque élément de nbf). On
trouve ci-après les programmes associés
avec les deux implémentations possibles du type
fraction.
program Un ; const num = 1 ; den = 2 ;
type entier = integer ; réel = real ;
fraction = array[num..den] of entier ;
(*$I Fraction.Inc *) {qui contient la procédure LitFraction} var nbe :
entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
writeln('donner un entier ') ;
readln ( nbe ) ;
writeln('donner aussi un réeel ') ;
readln ( nbr ) ;
writeln('et enfin une fraction ') ;
LitFraction( nbf )
end.
program Deux ;
type entier = integer ; réel = real ;
fraction = record num,den : entier end ;
var nbe : entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
writeln('donner un entier ') ;
readln ( nbe ) ;
writeln('donner aussi un réel ') ;
readln ( nbr ) ;
write('et enfin une fraction ') ;
write('(numérateur puis et dénominateur puis ) ') ;
readln( nbf.num );
readln( nbf.den )
end.
Pour garder l'homogénéité et la
correspondance avec l'algorithmique, on peut inventer
un code de lecture : 1 signifie entier, 2 signifie
réel, 3 signifie fraction. On obtient alors
program Trois ;
(*$I Fraction.Inc *) {qui contient la définition de Fraction}
type entier = integer ; réel = real ; var nbe, nbr, nbf : nombre ;
(*$I Lecture.Inc *) {qui contient la procédure Lit}
begin { Demande de nbe, nbr et nbf }
writeln('donner un entier ') ;
Lit( nbe , 1 ) ;
writeln('donner aussi un réel ') ;
Lit( nbr , 2 ) ;
writeln('et enfin une fraction ') ;
Lit( nbf , 3 )
end.
Pour que la procédure Lit puisse être
déclarée, il faut regrouper les trois
types entier, réel et fraction sous un
même type commun. On peut utiliser un
enregistrement comme
type nombre = record e : entier ; r : réel ; f : fraction end ;
ou même, si on ne veut pas perdre de place,
utiliser le code de lecture :
type nombre = record case typ : byte of 1 : (e : entier) ; 2 : (r : réel)
; 3 : (f : fraction) end { de case et de record }
Ce type d'écriture n'est pas satisfaisant. En
effet, il faut réécrire l'algorithme en
explicitant le numéro de code de lecture, ce qui
ne se justifie pas en algorithmique.
2.2 Surcharge et héritage
Regardons maintenant comment on peut résoudre ce
problème en termes d'objets. Inventons une
classe nommée Nombre avec trois sous-classes :
entier, réel et fraction. Cela s'écrit
en Pascal Objet :
type Nombre = Object ... end
Entier = Object( Nombre ) end
Réel = Object( Nombre ) end
Fraction = Object( Nombre ) end.
Mais en objet, les données et les actions sont
associées. La définition complète
est donc :
type Nombre = Object valeur : real ; (* même pour un entier *)
procedure Lire end
Entier = Object( Nombre ) end
Réel = Object( Nombre ) end
Fraction = Object( Nombre )
num, den : integer ; (* quand même ! *)
procedure Lire end
procedure Nombre.Lire ; begin
write(' Entrez un nombre ') ;
readln(Valeur)
end ;
procedure Fraction.Lire ; begin
write(' Entrez le numérateur ') ;
readln(Num) ;
write(' Entrez le dénominateur ') ;
readln(Den) ;
Valeur := Num / Den
end ;
Une remarque s'impose : la surcharge de Lire pour un
entier et un réel est remplacée par
l'héritage (ou transmission) de la
procédure Lire pour un objet. Par contre, si on
voulait vraiment que les objets entiers aient vraiment
le type entier, il faudrait redéfinir
Entier.Lire. Le programme principal associé
à de telles déclarations est
program Quatre ;
{... ici les déclarations d'objet précédentes }
var nbe : entier ; nbr : réel ; nbf : fraction ;
begin { Demande de nbe, nbr et nbf }
writeln('donner un entier ') ;
nbe.Lire ;
writeln('donner aussi un réeel ') ;
nbr.Lire ;
writeln('et enfin une fraction ') ;
nbf.Lire
end.
3. PROGRAMMATION (ORIENTEE) OBJETS
3.1. Présentation générale
Styles de programmation, modes de programmation ou
simplement mode des années 90, la Programmation
Objet (PO) et la Programmation Orientée Objet
(POO) sont très présentes dans
l'informatique, que ce soit sous forme de logiciels, de
livres ou simplement de sujet à discussion.
Nous essaierons ici de présenter la notion
d'objet et d'éclairer les différences
entre PO et POO. Pour cela, au travers du
problème classique de Comar on se servira de
divers langages représentatifs des
différentes tendances.
3.2. Notion d'Objets
En programmation classique, on compare souvent les
algorithmes à des recettes de cuisine pour bien
montrer l'aspect "déroulemental", la "technique
manipulatoire" du travail à effectuer. Pour
continuer l'analogie, de même qu'une recette
consiste en une liste d'ingrédients et en une
méthode de préparation, un objet est
composé de données et d'actions
liées à ces objets. Par exemple, une
disquette peut être identifiée par ses
caractéristiques (taille,secteurs etc.) et les
fonctions qu'elle supporte (lire, écrire etc.).
Cette description est plus un modèle de
disquette qu'une disquette réelle et en ce sens,
on parlera plutôt de la "classe" des disquettes
plutôt que de l'objet "théorique"
nommé disquette.
Quatre notions sont fondamentales en PO :
l'encapsulation, l'héritage et le polymorphisme
et l'envoi de messages. L'encapsulation est
l'intégration des différentes parties de
l'objet (données et programmes, structure et
opérations, champs et méthodes,
caractéristiques et opérations) dans un
même ensemble tout en masquant le détail
de l'écriture, comme dans le cas de
bibliothèques de programmes
précompilées. Un objet devient alors une
"boite noire" avec qui on ne communique que par
message. L'héritage permet transférer
des propriétés ou des actions d'une
classe à une sous-classe
spécialisée. Ainsi, la sous-classe
Ecrans issue de la classe
Périphériques_de_Sorties hérite de
l'action Initialisation. Cet héritage
définit une structuration à la fois des
données en elles- mêmes et des
données entre elles, ce que ne fait pas la
programmation classique. Au delà du
regroupement et de la cohérence des sous
programmes, cela donne une architecture des
donnés comparable à l'architecture des
programmes. Cela permet en particulier de retrouver
rapidement tout ce qui concerne un même type de
donnée (les entiers, les fichiers, les graphes
etc.). Le polymorphisme est le regroupement sous le
même nom d'actions différentes
liées aux objets. Ainsi les sous-objets de la
classe Polygones (tels triangles, rectangles etc.) ont
la même méthode Périmètre
dont la définition est différente suivant
l'objet. Associé à l'héritage, le
polymorphisme assure une
homogénéité quant aux actions, un
masquage de la définition. Le polymorphisme
dont un exemple simple est la surcharge des
opérateurs classiques (comme + qui peut
désigner l'addition de nombres, de fonctions, de
vecteurs... et la concaténation des
chaînes de caractères) permet une plus
grande lisibilité et un meilleur niveau
d'abstraction. L'envoi de messages se substitue
à l'écriture d'expression, l'appel de
sous programmes. Les instructions traditionnelles
argument_gauche opérateurO argument_droiteet
appeldu sousprogramme X avec l'argument A deviennent
respectivement l'envoi du message opérateurO
argument_droite à l'objet argument_gauche et
l'envoi du message X à l'objet A. Bien
qu'artificiel parfois (comment penser que dans 2 + 3
les nombres 2 et 3 ne jouent pas un rôle
symétrique mais qu'il y un le message + 3
envoyé à 2 ?) ce mécanisme de
transmission de message est très souple.
3.3. Conception par objets
De même de la programation modulaire, le
découpage en fichiers logiques et physiques
(fonction, procédures,sous programmes, et
fichiers inclus) représentent un progrès
par rapport à la programmation monobloc, la
programmation par objets permet un meilleur niveau
d'abstraction. L'abstraction provient tout d'abord
d'une simplification ou unification : au lieu de
manipuler des données et des programmes, on ne
traite qu'un seul type d'entité, les objets.
Ensuite, il y a un seul moyen de communication : les
messages. Enfin, le raffinement progressif dans
l'élaboration des solutions se font par
l'écriture de sous-classes ou de sur-classes.
La conception d'une application orientée objet
repose sur la construction d'une hiérarchie
d'objets exprimant la "généalogie" des
objets de l'application. On se méfiera,
toutefois de la dépendance du langage
sous-jacent : en PO, la gestion des objets
diffère beaucoup de celle de la POO. Ainsi, un
objet pour Turbo Pascal Objet ressemble à une
structure de type ENREGISTREMENT, qui n'est jamais
qu'une façon parmi tant d'autres
d'implémenter les objets.
Le schéma de développement en
programmation classique passe par une phase d'analyse
où les actions sont privilégiées.
Les données et les structures de données
ne sont intégrées qu'en tout dernier.
Ainsi, l'algorithme focalise la lecture d'un nombre
alors que l'implémentation et la traduction dans
un langage typé viendra spécifier s'il
s'agit d'un entier, d'un réel etc. même
s'il est difficile de justifier à priori le
choix (comment choisir entre le type real, single et
complex en Turbo Pascal ?). En PO, on se concentre
d'abord sur les classes d'objets, puis sur leurs
caractéristiques et enfin sur les actions.
Cette programmation permet de mélanger plus
intimement algorithme et données, d'examiner les
rapports entre code et valeur. On viendra par exemple
dans le cas de saisies multiples recenser les
différents types d'objets à saisir,
choisir quelles vérifications on imposera avant
de détailler chaque classe.
4. Exemple de PO pour le problème de Comar
4.1. Vocabulaire de la PO
Le vocabulaire traditionnel de la PO comprend les
termes de Classes, d'Objets, de Champs, de
Méthodes, de Messages et d'Instances. Ces
différentes notions peuvent être
présentées dans la résolution en
PO du problème de COMAR. Une classe est la
définition "théorique" de l'objet, son
moule, sa "déclaration" au sens d'une
énumération de ses possibilités.
Ainsi la classe Phrase est caractérisée
par sa longueur, son texte. Les "opérations"
sur Phrase sont la lecture, la noelisation... Les
champs d'un objet sont les données
associées, les méthodes sont les
programmes associés. Ainsi longueur et texte
sont des champs de Phrase, lecture et noelisation des
méthodes de Phrase (mais on aurait pu
définir longueur comme une méthode). Les
objets comuniquent entre eux et avec le monde
extérieur par message. L'envoi d'un message est
donc similaire à l'appel d'une procédure,
d'une fonction. Une instance de la classe Phrase est
la réalisation effective d'un objet de type
Phrase. La création d'une instance est
différente du remplissage des champs de cette
instance qui se fait soit par héritage soit par
messages.
4.2. Application au problème de Comar
Une instance de la classe Phrase est la
réalisation effective d'un objet de type Phrase.
La création d'une instance peut être
différente du remplissage des champs de cette
instance qui se fait soit par héritage soit par
messages. Ainsi, le remplissage des chamlps longueur
et texte d'un objet Phrase sera certainement
réalisé par la méthode Lecture.
Dans le problème de Comar, la donnée de
départ est une PHRASE composée d'un
ensemble de MOTS qu'il faut DENOMBRER, INVERSER et qui
doit ensuite être RETRANSCRITE en arbre de noël
avec pour sommet son MILIEU. A priori, deux OBJETS
émergent immédiatement : l'objet PHRASE
et l'objet MOT qui héritera de ses CHAMPS et de
ses METHODES. L'objet PHRASE aura notamment pour
CHAMPS sa longueur (nombre de caractères), sa
cardinalité (nombre de mots) et pour METHODES la
lecture, la recherche du milieu, la "Noëlisation"
(écriture en arbre de noel), l'inversion
(retournement). L'objet MOT héritera de
longueur, de lecture et d'inverse. Si on
préfere, on puet ne créer qu'un objet
(Chaine) dont Phrase et Mot sont des instances, car
finalement, un mot peut être
considérée comme une phrase. On peut
généraliser le problème de Comar
en demandant d'afficher en arbre de Noël soit une
phrase, soit un mot, soit un vecteur (liste de nombres)
ou une liste de symboles Dans tous ces cas, on doit
décomposer une collection en
éléments et la réécrire en
arbre de noel. On peut donc créer une
sur-classe de Phrase nommée Collection dont les
champs sont sa cardinalité (nombre
d'éléments) et ses
éléments, dont des méthodes sont
lecture, noelisation, inversion. C'est à ce
niveau que la PO prend tout son sens : le temps de
développement passé à rajouter une
nouvelle collection est très réduit
puisqu'on n'aura pas à rechercher les sources,
à réécrire ou à recopier en
modifiant légèrement les entrées,
les sorties.
Une présentation simplifiée de la
hiérarchie des objets correspond à
Collection
Phrase
Mot
Vecteur
Nombre
Entier
Rationnel
...
Liste
Symbole
etc.
Le graphe d'héritage complet associé est
alors :
Objet Collection Champ Cardinalité
+ Champ Eléments
+ Méthode Lecture
+ Méthode Noelisation
+ Méthode Inversion
+-----Objet Phrase Champ Cardinalité (hérité)
+ + Champ Eléments (hérité)
+ + Champ Longueur (propre)
+ + Méthode Lecture (héritée)
+ + Méthode Noelisation (héritée)
+ + Méthode Inversion (héritée)
+ +-----Objet Mots Champ Longueur (hérité)
+ Champ Contexte (propre)
+ Méthode Lecture (hérité)
+-----Objet Vecteur Champ Cardinalité (hérité)
+ + Champ Eléments (hérité)
+ + Méthode Lecture (héritée)
+ + Méthode Noelisation
+ + Méthode Inversion
+ +-----Objet Nombre Champ Valeur (propre)
+ Champ Signe (propre)
+ Méthode Lecture (héritée)
+-----Objet Liste Champ Cardinalité (hérité)
+ + Champ Eléments (hérité)
+ + Méthode Lecture (héritée)
+ + Méthode Noelisation
+ +-----Objet Symbole Champ Valeur (propre)
+ Méthode Lecture (héritée)
etc.
Le degré d'abstraction et de raffinement
introduit par la PO est clair : au lieu de
définir de nombreuses structures de
données similaires (comme en Pascal) et de
réécrire les procédures
associées, la PO permet de partir d'un type de
structure et de ses procédures pour ensuite
dériver d'autres structures, que ce soit des
sous-structures ou des sur-structures.
Concrètement, cela rejoint la technique de
"refinement" (raffinement) de certains types de
programmation. Au fur et à mesure que l'on
programme, on crée ainsi des hiérarchies
d'objets. De plus, et la cohérence (ou
seulement la liaison de par le même traitement)
entre Phrase, Vecteur, Liste se retrouve grâce
à la clase Collection. Le Pascal classique, ne
le permet pas. Ainsi, pour afficher une liste de
caractères un par un stockés dans un
tableau de caractères nommé T de taille n
, on utilise la boucle for k := 1 to n do begin
writeln( T[k) end ; de même, pour afficher les
différents éléments d'un tableau X
on écrit for k := 1 to n do begin writeln( X[k)
end ;sans qu'il soit possible de regrouper ces
instructions en une seule, à cause de la nature
des termes utilisés. Même si ces
instructions sont courtes, le même
problème se pose pour des procédures.
Ainsi, le tri d'une liste de nombres et le tri d'une
liste de mots ne peut se faire en Pascal avec la
même procédure, alors que la seule
différence se situe au niveau de la
déclaration de l'objet trié !
En PO, l'héritage permet d'utiliser une
métthode pour diféfrents objets, puisque
la notion de type n'est pas explicité. Si
toutefois on le désire, on peut redéfinir
à l'intérieur d'une classe une
méthode si l'héritage n'est pas
suffisant. Par exemple, pour un problème de
Comar général, on pourrait envisager un
graphe d'héritage bâti sur la notion de
collection.
4.3. Intérêts et difficultés de la PO
Ce type de programmation n'est pas pour autant plus
facile, plus naturel que la programmation
traditionnelle. Tout d'abord il faut être
capable de structurer la hiérarchie, savoir
faire dériver, hériter correctement. La
qualité du logiciel associé est
lié à une non redondance, un masquage des
procédures les plus internes. Avant d'inventer
un objet, il faut donc vérifier qu'il n'a pas
déjà été inventé,
comme en programmation classique. Avec un langage
comme Smalltalk, il y a en standard environ 100 objets
et 2000 méthodes. S'y retrouver
représente un gros travail. Par exemple, il
serait incohérent de réinventer le calcul
de la moyenne d'une liste de nombres si elle existe
déjà. Parcourir l'encyclopédie
des classes (partie 4 du manuel de Smalltalk) où
utiliser en interactif le Class Hierarchy Browser prend
beaucoup plus de temps que d'écrire la
méthode nommée moyenne pour l'objet liste
de nombre. Le masquage des procédures donne une
programmation plus aisée : on travaille comme
avec une bibliothèque de sous programmes dont on
ne connait que la syntaxe d'appel, les conditions
d'utilisations, mais pas le texte source. Cela ne
garantit pas pour autant de la "bonne" écriture
de ces procédures. Un mauvais programmeur fera
un piètre programmes en PO. Prenons un exemple
concret : supposons que nous redéfinissions la
conversion en majuscule d'une chaine de
caractères par programme, sachant qu'on dispose
d'une conversion caractère par caractère
seulement. Nous voulons utiliser pour cela une boucle
REPETER ... JUSQU'A. Notre idée (fausse) de
départ est qu'on doit traiter les
caractères 1 à n où n
désigne la longueur de la chaine. Pour une
chaine nommée CH l'algorithme itératif
associé est
N * longueur(CH)
{ on parcourt la chaine caractère par caractère
noté K et repéré par son indice IC }
IC * 1
répéter
K * SousElément(CH,IC)
... { conversion de K dans CH }
IC * IC + 1
jusqu'à IC = N
L'erreur de notre méthode est de ne pas prendre
en compte la chaîne vide. Faire de notre
algorithme une fonction ou une procédure ne
résoudra pas le problème, pas plus que
d'en faire une méthode pour les objets
associés aux chaines de caractères. Une
autre difficulté de la PO qui ne la rend pas
immédiatement accessible est qu'elle ne prend
tout son sens sert que pour des "gros" programmes.
Pour un programme court et jetable (et il y en a
beaucoup), la PO n'est pas forcément rentable.
Par contre, des environnements complets (tels
Smalltalk) présentent un "plus" par rapport
à des langages orientés objets : la
diminution du nombre de fichiers, par exemple, est
flagrante. Smalltalk contient tout en une seule
"image". On est loin des fichiers sources, des
fichiers inclus, des fichiers de données, des
fichiers de code relogeable, des fichiers de code
exécutable etc.
4.4 Langages :
Quatre langages différents viendront
détailler les différences entre PO et
POO, à savoir : Smalltalk, Lisp/Flavors, Turbo
Pascal Objet et C++. Plus qu'un exercie de style ou
d'école, le passage par ces divers langages
viendra montrer les pièges et les oppositions de
concepts, de développement. On s'attachera
donc, après l'écriture des programmes
à une comparaison critique. SMALLTALK est le
langage objet par excellence car tout n'y est qu'objet.
Smalltalk est écrit en Smalltalk (sauf le noyau
écrit en assembleur). Il intègre les
objets et leur environnement dans un tout
homogène et graphique. Le passage par ce
langage de référence est obligé.
LISP/FLAVORS est aussi un standard. Si la PO et l'IA
sont liés, ce langage en montrera les liaisons,
les apports réciproques.
TURBO PASCAL Orienté Objets est
dérivé de Pascal, langage très
connu en milieu universitaire. L'utiliser permettra de
dégager les bienfaits ou les problèmes de
la POO. C++ est une référence en termes
de développement industriel (ou au moins
posé comme tel).
5. Le langage Smalltalk
Un exemple simple de programme Smalltalk est celui qui
correspond à un dialogue de "bonjour". Le
programme affiche Bonjour sur l'écran, puis
demande un prénom. Il affiche ensuite sur la
ligne suivante la date et l'heure puis écrit "au
revoir" suivi du nom de la personne en majuscules.
Exemple d'exécution : si monsieur TEST lance le
programme à 11 h 30 : 12 le 12/01/1992, on aura
sur l'écran :
Bonjour Quel est votre prénom ? Test Le 12
janvier 1992 à 11:30 ; au revoir, TEST.
L'algorithme correspondant ne présente aucune
difficulté. Il fait référence aux
fonctions date, heure et maju. On aurait pu
écrire date() et heure() au lieu de date et
heure, MCRIRE au lieu de MECRIRE (qui signifie,
rappelons "écrire sur la même ligne sans
passer à la ligne suivante") pour garder autant
de lettres que dans le mot ECRIRE.
{ Algorithme de BONJOUR }
ECRIRE "Bonjour."
MECRIRE "Quel est votre prénom ?
" LIRE pren
ECRIRE "Le ",date," à ",heure," au revoir, ",maju(pren)
L'exemple un met en jeu la notion d'entrée et de
sortie, de conversion en majuscules, de
récupération de la date et de l'heure.
En Smalltalk, on écrit :
bonjour " Exemple de commentaire, lire, écrire et affectation "
| pren |
Cursor offset: 140 @ 150.
pren := Prompter prompt: ' Bonjour. Quel est ton prénom ? '
default: ''.
pren := ' Merci. Au revoir ' ,
(pren asUpperCase), '' ,
(Date today) printString.
Cursor offset: 140 @ 300.
Menu message: pren.
" fin de bonjour "
Ce programme appelle quelques commentaires. Les
programmes sont écrits en format libre, mais
avec respect des majuscules et minuscules pour les
identificateurs. Les instructions sont
séparées par des points (et l'indentation
les met en évidence). Les variables en
majuscules correspondent à des variables
globales ; certaines méthodes ont un : en fin
pour indiquer qu'il faut des paramètres ; pren
est une variable locale (indiqué par les |).
Ainsi Cursor, Prompter, Date, Menu sont des variables
globales, offset:, prompt:, default:, asUpperCase,
today, printString, message: sont des méthodes.
Smalltalk travaille uniquement en mode graphique
fenêtré. Pour avoir le même
affichage à chaque exécution, on indique
une position curseur (sinon, l'affichage est
effectué en fonction de la position courante de
la souris). La demande de pren est accompagné
d'une valeur par défaut, ce qui est une bonne
chose. ECRIRE puis MCRIRE de l'algorithme ne sont pas
traduits. Il est en effet difficile de
réécrire de façon simple
(c'est-à-dire sans passer par les objets
d'entrées:sorties de type "stream") à la
suite du dernier texte écrit. La fin du
programme par Menu permet d'attendre une réponse
utilisateur. Sinon, comme en Pascal, le programme se
termine et l'affichage disparait. Pour sauvegarder ce
programme (qu'on a pu écrire dans le Transcript,
par exemple), il faut l'associer à un objet.
Ici, par exemple, on pourra en faire une méthode
d'un objet "démonstration". Le nom de la
méthode sera bonjour. On mettra donc en
en-tête :
bonjour " Exemple de commentaire, lire, écrire et affectation "
Les commentaires entre " sont facultatifs. Une fois
l'ensemble du texte sélectionné, on
appelle le "Browser" qui gère les objets et leur
métodes. On se positionne sur la classe
Démo et utilise l'option File du menu. Pour
réutiliser la méthode Bonjour, dans le
Transcript, on ne peut taper bonjour puis
l'exécuter car en objet, tout message a un objet
récepteur (seuls les paramètre sont
facultatifs). Il faut donc créer un objet de
type démo (ou en retrouver un). Dans le premier
cas, on exécute
| tstDemo |
tstDemo := DemoClass new.
tstDemo bonjour
Dans le second cas, on cherche avec le dictionnaire (en
exécutant Smalltalk inspect et en descendant
dans la liste des variables) un objet global de type
demo. On trouve par exemple Demo DemoGil Gil. On peut
alors exécuter
Demo bonjour
Pour connaitre la classe d'un objet, par exemple pour
savoir comment 2 est reconnu, il suffit de taper
2 class
Le système répond alors : SmallInteger.
De même, SmallTalk est de type SystemDictionnary.
On peut parcourir une classe par edit et une variable
par inspect. Ainsi, on peut taper
2 inspect
Smalltalk inspect
DemoClass edit.
Comme deuxième exemple, prenons la table de
multiplication. L'algorithme est :
ECRIRE " De quel nombre voulez-vous la table ? "
LIRE nb
ECRIRE " Table de multiplication de " , nb
POUR indr DE1A 10
produit <-- indr * nb
ECRIRE indr , " fois " nb , " = " , produit
FIN_POUR indr { DE1A 10 }
L'écriture en Smalltalk se fait en plusieurs
temps : on écrit le module principal comme une
méthode de l'objet démo, soit le
programme :
tabmult " Table de multiplication "
| nb |
nb := Prompter prompt: 'Donner un entier de 1 à 10 '
default: '3'.
(nb asInteger) table.!
" fin de tabmult "
Ce programme fait appel au module table. Celui-ci est
une méthode associée à un objet de
type integer. On peut donc exécuter dans le
Transcript, indépendamment du programme
précédent l'instruction "4 table". Le
détail de table suit.
table " de multiplication "
| fenetre |
fenetre := TextEditor windowLabeled: 'Table de Multiplication '
frame: (210 @ 30 extent: 400 @(Display extent //2)y).
1 to: 10 do: [ :indr | fenetre nextPutAll: indr format ;
nextPutAll: ' fois ' ;
nextPutAll: self printString ;
nextPutAll: ' = ' ;
nextPutAll: (indr * self) format ;
cr.
].
fenetre activate.
Menu message: 'Cliquez en gauche ici pour terminer'.
" fin de table "
La méthode table utilise une méthode
nommée format. Nous avons volontairement
compliqué format puisqu'elle apelle format:
(ceci pour montrer le passage de paramètres et
la variable self). Le symbole ^ indique que la
méthode renvoie le résultat de
l'instruction qui suit le symbole ^ (à
l'esclusion des commentaires).
format " Formate sur 3 caractères un entier "
^ self format: 3 " fin de format "
format: nbdeCar " Formate un entier sur nbdeCar caractères "
| chaine |
chaine := self printString.
1 to: (nbdeCar - (chaine size)) do: [ :ind | chaine := ' ' , chaine ].
^chaine " fin de format: "
Il faut remarquer que format, format: peuvent
être utilisées par n'importe quel
programme, n'importe quelle méthode. Les
méthodes sont donc des modules dynamiques, par
raport aux modules des bibliothèques classiques.
De plus, elles ne sont pas liées à un
fichier particulier : elles font partie de Smalltalk.
L'ensemble de Smalltalk est donc ouvert et
sauvegardé dans un fichier image (qui est donc
gros - de l'ordre du Méga). Le Transcript aussi
et toutes les interactions sont aussi
sauvegardées, ce qui permet de défaire
tout ce qui a été fait, de retrouver des
essais, des valeurs... Il est hors de question de
présenter les 100 objets et 2000 méthodes
(environ) disponibles en standard avec Smalltalk.