230
1er septembre 2002 Prof. Eric Lefrançois Ecole d’Ingénieurs de l’Etat de Vaud Programmation Objet Avec Java

Programmation Objet Avec Java

  • Upload
    others

  • View
    18

  • Download
    0

Embed Size (px)

Citation preview

Page 1: Programmation Objet Avec Java

Ecole d’Ingénieurs de l’Etat de Vaud

Programmation Objet Avec Java

1er septembre 2002Prof. Eric Lefrançois

Page 2: Programmation Objet Avec Java

ELS - 27 octobre 2002 - Entete.fm

Page 3: Programmation Objet Avec Java

POO avec Java - I -

Contenu

1 PRÉAMBULE 1

2 OBJETS ET TYPES ABSTRAITS 3

2.1 LA PROGRAMMATION-OBJET: NOTION «D’OBJET» 4Un exemple: un objet de type «Rectangle» 4Le cycle de vie d’un objet 6

2.2 TYPE ABSTRAIT: DÉFINITION 7Un type abstrait définit une «structure de données».. 7Un type abstrait est défini par un «ensemble d’opérations».. 8

Définition du type abstrait «Rectangle» en Java 11Implémentation du type abstrait en Java 13Création de l’objet: l’opérateur «new» 15Destruction de l’objet: le message «finalize()» 15Structure d’une classe Java 16Encapsulation de la structure d’informations 17Un type abstrait peut être défini directement par une classe 18Type courant et type déclaré 18Interface ou classe ? 20Une interface pour déclarer des constantes de configuration 23

2.3 SÉMANTIQUE DES OPÉRATIONS 24Description informelle de la sémantique 25Description formelle de la sémantique 25

La notion «d’expression» 25Exprimer la sémantique des opérations 27

La programmation «par contrat» 28Moyens mis à disposition en Java: instruction «assert» 29Un exemple de programmation par contrat avec l’opération «agrandir» 30

2.4 DÉFINIR LA STRUCTURE DE DONNÉES: LES TYPES DE JAVA 31Les types primitifs 32Les types «objets» 34Partage d’objets, copie superficielle et copie profonde 35

Les primitifs sont manipulés par valeur 35Les objets sont manipulés par référence 36Copie d’objets 36

Passage de paramètres 38

Page 4: Programmation Objet Avec Java

e-Quest Analyse - II -

2.5 VARIABLES DE CLASSES ET VARIABLES D’INSTANCE 39Initialisation d'une variable de classe 41Accéder aux variables de classe et aux variables d’instance 41Le constructeur de classe 41

2.6 POURQUOI «ABSTRAIT» ? 42

2.7 INTÉRÊT DES TYPES ABSTRAITS 42On s’intéresse d’abord aux opérations.. 42Indépendance par rapport à l’implantation.. 43Un exemple.. ré-implantation de la classe «Rectangle» 43

2.8 LA PROGRAMMATION OBJET ET LES TYPES ABSTRAITS 45Programmation objet versus programmation procédurale 45Un objet est-il un type abstrait ? 47Classes et types abstraits 48

2.9 LA STRUCTURE D’UN PROGRAMME 48Les paquetages 48Organisation des fichiers sources 50Amorce de l’application: la classe principale (main) 50

3 HÉRITAGE ET POLYMORPHISME 53

3.1 JAVA ET LE MÉCANISME D’HÉRITAGE 54Héritage simple contre héritage multiple 54Héritage simple en Java 56

Un exemple théorique 58Héritage: quel intérêt? 59

Une meilleure structuration du programme 59La réutilisation de code 59L'héritage et l'utilisation de la mémoire 59

Edition de liens dynamique 60Objets: type de déclaration et type courant 61

3.2 UN BILAN SUR LES MODES DE PROTECTION 64Encapsulation 65Le mode privé: «private» 65Le mode public: «public» 66Le mode protégé: «protected» 66Le mode «paquetage »: mode par défaut 68Conseils et recommandations 68

3.3 ENCHAÎNEMENT DES CONSTRUCTEURS 71Invocation implicite du constructeur de la superclasse 71Le constructeur par défaut 73

3.4 BIEN UTILISER L'HÉRITAGE 74L'héritage, vu comme un outil de spécialisation 74La relation «est-un» 77Le sous-typage 78

Rappel : la notion de type 79Notion de sous-type 79

La règle de substitution 79

3.5 LA REDÉFINITION DES MÉTHODES 80Redéfinition et surcharge: parfois la confusion 80Redéfinition: répondre à la règle de substitution 82Premier corollaire: est-il possible de changer le mode de protection ? 83

Page 5: Programmation Objet Avec Java

e-Quest Analyse - III -

Deuxième corollaire: les exceptions et la redéfinition 84Interdire la redéfinition et bloquer l’héritage 84

Les « méthodes finales» 84Les «classes finales» 85

3.6 LE POLYMORPHISME 85Généralités 85Comparons Java, Smalltalk et C++ 86De l'intérêt du polymorphisme 87Méthodes et classes abstraites 87

3.7 MASQUAGE D’UNE VARIABLE D’INSTANCE 92

3.8 LE MOT-CLÉ «SUPER» 93

3.9 VARIABLES DE CLASSE ET HÉRITAGE 94

3.10 MÉTHODES DE CLASSE ET HÉRITAGE 95

4 LES MODÈLES DE CONCEPTION 97

4.1 EN PRÉLIMINAIRE 98

4.2 LE MODÈLE MVC : «MODELE-VUE-CONTRÔLEUR» 98Modèle 99Vue 99Relations entre le MODELE et les VUES 101Le modèle «Observateur» 101

Diagramme de classe 102Une classe «Observable» réutilisable 102Diagramme de séquence 103

Contrôleur 103

4.3 LE MODÈLE «OBSERVATEUR» 104

4.4 LE MODÈLE «COMPOSITE» 104Un exemple: une image graphique 105Un autre exemple : la hiérarchie des composants de la librairie «awt» de Java 107

5 OBJETS ACTIFS 109

5.1 GÉNÉRALITÉS SUR LES THREADS 110Concurrence ou parallélisme ? 113Mise en oeuvre des threads en Java 114

5.2 LES THREADS DE LA MACHINE VIRTUELLE 115

5.3 DIAGRAMME D’ÉTATS ET DE TRANSITIONS 117Construction d’un thread 117Lancement d’un thread: start() 117Blocage d’un thread (sleep(), suspend() et entrée-sortie) 119Mort d’un thread 120Récupération d’un thread «mort» par le garbage collector 120Contrôler l’état d’un autre thread: isAlive() 121Attendre la terminaison d’un thread: join() 121

5.4 RÉALISER DES OBJETS ACTIFS EN JAVA 121Les objets passifs 121Le objets actifs 122Réalisation d’un «objet actif» en Java 122Méthode no1: par dérivation de la classe «Thread» 123

Page 6: Programmation Objet Avec Java

e-Quest Analyse - IV -

Méthode no2: par implémentation de l'interface «Runnable» 124

5.5 SCHEDULING: POLITIQUE D’ALLOCATION DU PROCESSEUR 124La priorité des threads 124Règles d’ordonnancement des threads 125Le modèle préemptif de Java 127En conclusion 129

5.6 EXCLUSION MUTUELLE: VERROUILLAGE D’UN OBJET 129Le mot-clé synchronized 131Java et les moniteurs 131Fermer le verrou 132Ouvrir le verrou 133Un «bloc synchronisé» 133Une «méthode synchronisée» 133Blocs synchronisés ou méthodes synchronisées ? 134La synchronisation de méthodes statiques (méthodes de classes) 134

5.7 RENDEZ-VOUS ENTRE THREADS 135Sémantique de «wait()» 137Sémantique de la méthode «notify()» 138Sémantique de la méthode « notifyAll()» 139

Un modèle standard pour utiliser «wait()» 139Exemple: une file d'attente 140

5.8 LES VARIABLES «VOLATILE» 141

5.9 LES THREADS DÉMONS 142

5.10 LES GROUPES DE THREADS 142

5.11 LES MÉTHODES «STOP()», «SUSPEND()» ET «RESUME()» SONT DÉPRÉCIÉES DANS JAVA 1.2143Les dangers du message stop(), comment le remplacer 144Les dangers du message suspend(), comment le remplacer 145

5.12 ANNEXES 147Méthodes de la classe «Thread» 147

start() 147stop() 147sleep(...) 147suspend () 148resume() 148isAlive() 149yield() 149interrupt() 149interrupted() 149isInterrupted() 150setDaemon() 150isDaemon() 150join() 150

Méthodes de la classe «Object» 150wait(..) 150notify() 151notifyAll() 151

6 ETUDE DE CAS UNE APPLICATION CLIENT-SERVEUR153

Page 7: Programmation Objet Avec Java

e-Quest Analyse - V -

6.1 ENTRÉES/SORTIES SOUS UNIX: LES SOCKETS 154Les fichiers spéciaux 154Les «sockets» 155

Communication fiable 155Communication non fiable de paquets 156

6.2 JAVA, LE MODÈLE CLIENT-SERVEUR ET LES SOCKETS 157Le modèle «Client-Serveur» 157Le modèle Client-Serveur de l'application «chat» 158

Liaisons UDP et connexions TCP-IP 158Protocole TCP 158Protocole UDP (datagrammes) 159

Principe d'utilisation des sockets TCP 160Du côté «Client» 160Du côté «Serveur» 160Construire l'adresse IP 161Fermeture de la connexion 163Ouverture simultanée de plusieurs connexions 163Transmettre et recevoir des informations au travers d'une socket 164

6.3 ILLUSTRATION: LE PROGRAMME «CHAT» 165Interface utilisateur 166

Côté «Serveur» 166Côté «Client» 166

Le «Client»: diagramme des classes 169Classe «Client» 170Classe «ServeurEcouteur» 171Classe «InterfaceUtilisateur» 171Classe «FenetreApplication» 171

Le «Serveur»: diagramme des classes 172Classe «Serveur» 173Classe «Connexion» 173Classe «ControleurConnexions» 173

Test de l'application 174Risque d'interblocage 174

6.4 ANNEXES 175A0: Protocole Client - Serveur 176A1: Le «Client»: diagramme des états et transitions 178A2: Le «Serveur»: diagramme des états et transitions 179A3: Listing du fichier Client: «Client.java» 180A4: Listing du fichier Client: «ServeurEcouteur.java» 186A5: Listing du fichier Client: «InterfaceUtilisateur.java» 189A6: Listing du fichier Serveur: «Serveur.java» 194A7: Listing du fichier Serveur: «Connexion.java» 196A8: Listing du fichier Serveur: «ControleurConnexions.java» 199A9: Classe «Socket» 200A10: Classe «ServerSocket» 201A11: Classe «InetAddress» 201

Annexe A UML: UN RÉSUMÉ DU DIAGRAMME DE CLASSES 2036.5 LES DIAGRAMMES 204

6.6 DIAGRAMMES DE CLASSES 204

6.7 CLASSES 205

Page 8: Programmation Objet Avec Java

e-Quest Analyse - VI -

Nom de la classe 205Attributs 206Opérations(Méthodes) 206Variables et méthodes de classe 206Visibilité des éléments 207Autres propriétés des éléments 207

6.8 CLASSES UTILITAIRES 208

6.9 OBJETS 208Nom de l'objet 208Valeur de l'objet 208Multiplicité 209

6.10 MULTIPLICITÉ DES OBJETS, EXEMPLE D'UN FEU DE LA CIRCULATION 209

6.11 ASSOCIATIONS 209Nom de l'association 210Rôle des classes 210Cardinalité 210Associations multiples 211Association-OU 211

Agrégations 211Cardinalité du composite 212

Associations ternaires 212Propriété de «navigabilité» 213

6.12 HÉRITAGE 214Généralisation-OU 214Généralisation-ET 214Type de partition 215

6.13 PAQUETAGES (PACKAGES) 216Référence à un autre paquetage 217Dépendances entre paquetages 217

Annexe B INDEX 219

Page 9: Programmation Objet Avec Java

POO avec Java - 1 - Préambule

1 PréambuleCe manuel est destiné aux étudiants désireux d'être introduits aux concepts qui forment la base de la philosophie objet.

Dans une première partie, nous commencerons par couvrir les notions d'objet et de type abstrait. Nous continuerons par une discussion en profondeur sur l'héritage et le poly-morphisme, puis nous nous attarderons sur la présentation détaillée de quelques uns des modèles de conception fondamentaux qui se rattachent au paradigme objet comme les modèles «Observateur», «MVC» (Modèle-Vue-Contrôleur), etc..

Enfin, nous terminerons par une étude de cas, une application Client-Serveur distri-buée, qui nous permettra notamment d'illustrer la structure d'un programme comportant des objets concurrents.

Les exemples développés au travers du manuel sont exprimés en Java, un langage qui a su faire la synthèse de différents outils qui ont connu leur heure de gloire comme Smalltalk ou C++.

La structure des programmes, les diagrammes d'état, diagrammes de classes et de séquence sont présentés en utilisant le formalisme UML («Unified Modeling Lan-guage») dont quelques aspects sont présentés en annexe.

ELS - 14 novembre 2002

Page 10: Programmation Objet Avec Java

POO avec Java - 2 - Préambule

ELS - 14 novembre 2002

Page 11: Programmation Objet Avec Java

POO avec Java - 3 - Objets et types abstraits

2 Objets et types abstraits

Pour introduire le lecteur à la notion d’objet, nous nous appuierons essentiellement sur la notion de type abstrait, - un concept considéré comme un classique de la programma-tion -, que nous aborderons dans une perspective propre à la programmation orientée ob-jet.

Se basant sur les concepts élaborés par [Booch]1, nous commencerons par une pre-mière définition du concept d’objet.

Puis, considérant dans une première approche qu’un objet peut être considéré comme une structure de données définie par un ensemble d’opérations, nous enchaînerons par une présentations du concept de type abstrait.

Nous terminerons en montrant que le concept d’objet présente un certain nombre de richesses qui sont absentes du concept de type abstrait. Nous citerons notamment:

l’héritage de la structure et du comportement,

1. Grady Booch - Auteur de nombreux ouvrages de référence portant sur la conception orientée objet

ELS - 3 février 2003

Page 12: Programmation Objet Avec Java

POO avec Java - 4 - Objets et types abstraits

le fait qu’un objet peut représenter autre chose qu’une structure de données «passive». En effet, un objet peut modéliser un «automate», qui, associé à un programme spécifique peut s’exécuter en concurrence avec les autres objets du programme.

2.1 LA PROGRAMMATION-OBJET: NOTION «D’OBJET» En voulant formaliser le paradigme objet, [Booch] a défini l’objet informatique comme un élément caractérisé par 3 composantes:

1. Un état (ou une «valeur»): état de la mémoire occupée par l’objet; 2. Un comportement: ensemble des opérations qui permettent d’agir sur l’objet; 3. Une identité: l’objet existe indépendamment de sa valeur.

2.1.1 Un exemple: un objet de type «Rectangle» Imaginons une application graphique qui manipule différentes sortes de figures: des cer-cles, des rectangles, des carrés,..

Du point de vue du programmeur qui aura développé ce logiciel, ces figures sont consi-dérées comme des objets..

Considérons par exemple l’objet «r1», un objet de type «Rectangle». Un tel objet est caractérisé par 4 informations variables: les coordonnées x et y de son point supérieur gauche, sa largeur et sa hauteur.

1. «r1» a un état: la valeur prise par les 4 informations qui le caractérisent;Par exemple: <x=3, y=4, largeur = 20, hauteur = 10>.

2. «r1» a un comportement: ensemble des opérations qui permettent d’agir sur «r1»;Par exemple:

r1 = new Rectangle (3, 4, 20, 10);Création de «r1» avec la valeur initiale <3, 4, 20, 10> r1.déplacer (5, 6);Pour déplacer le rectangle de «5» en X, et de «6» en Y.Son état a été modifié. Il vaut maintenant <8, 10, 20, 10> r1.agrandir (3); Pour augmenter de «3» la largeur et la hauteur du rectangle.

X(0,0)

Y

y

x

largeur

hauteur

ELS - 3 février 2003

Page 13: Programmation Objet Avec Java

POO avec Java - 5 - Objets et types abstraits

taille = r1.getLargeur();Pour retourner la largeur du rectangle, affectée à la variable «taille» Les 3 opérations «déplacer», «agrandir» et «getLargeur» ont été invoquées en «envoyant un message» à l’objet.

Notons au passage la syntaxe d’un envoi de message:

<objet>.<message_avec_paramètres>

3. «r1» a une identité: Le rectangle «r1» existe, indépendamment de sa valeur. Sa valeur peut être modifiée notamment par les opérations «déplacer» et «agrandir». «r1» est une variable qui sert à désigner le rectangle. Le même rectangle pour-rait être désigné par une autre variable. Ce rectangle existe indépendamment de la variable ou des variables qui servent à le désigner dans le programme. Par exemple:

r1 = new Rectangle (3, 4, 20, 10);

Le nouveau rectangle de valeur <3,4,20,10> est désigné par la varia-ble «r1»

r2 = r1;

Le même rectangle est désigné par une deuxième variable, la variable «r2»

r1 = new Rectangle (3, 4, 20, 10);

Notre rectangle continue d’exister, il est toujours désigné par la variable «r2». La variable «r1» est maintenant utilisée pour désigner un autre rectan-gle, qui, - par hasard -, a la même valeur que le premier rectangle.

r1

20

10

<3,4>

r1

20

10

<3,4>

r2

<3,4,20,10>r1 <3,4,20,10>

r2

ELS - 3 février 2003

Page 14: Programmation Objet Avec Java

POO avec Java - 6 - Objets et types abstraits

Définition 1: La notion d’Objet

Un objet est caractérisé par 3 éléments:

1. un état (ou une «valeur») Un objet occupe une place en mémoire. Cette place mémoire permet de stocker la valeur courante des différentes variables qui le caractérisent. Ainsi, un objet rassemble un certain nombre d’informations. On dit qu’un objet est une struc-ture de données. Le mot «donnée» est synonyme «d’information». En program-mation, chaque information (chaque «donnée») est représentée par une variable.

2. un comportementUn objet réagit à un certain nombre d’opérations qui permettent «d’agir sur» cet objet. Le programmeur enclenche de telles opérations en «envoyant un mes-sage» à l’objet. En réponse, l’objet pourra modifier son état, et pourra agir sur d’autres objets en leur envoyant des messages à son tour.

Notons que le comportement d’un objet peut différer selon son état, ce qui apparaît dans le paragraphe suivant [Voir paragraphe - Le cycle de vie d’un objet - page 6]

3. une identitéL’identité d’un objet caractérise le fait que l’objet existe: il possède un «cycle de vie» qui commence par sa création et se termine par sa destruction. Dans le contexte de la programmation, dire qu’un objet «possède une identité», c’est affirmer que cet objet existe, et que cette existence est absolument indépen-dante de la valeur de cet objet et de la façon dont on le désigne. Autrement dit, l’objet est un «individu» unique, dont la valeur peut être modifiée, et qui peut avoir plusieurs noms. Notons que deux objets différents peuvent avoir la même valeur !

2.1.2 Le cycle de vie d’un objetLa vie d’un objet peut être caractérisée par un diagramme d’états et de transitions, qui présentera la création de l’objet, sa destruction, ainsi que les différents états caractérisés chacun par un comportement spécifique de l’objet.

Voici par exemple le diagramme d’états et de transitions d’une pile (LIFO), représenté en utilisant la notation UML. Un tel objet est susceptible de répondre aux 3 messages suivants:

1. empiler(unElement) - Procédure2. depiler(): unElement - Fonction3. estVide(): boolean - Fonction

ELS - 3 février 2003

Page 15: Programmation Objet Avec Java

POO avec Java - 7 - Objets et types abstraits

La réception de l’un ou l’autre de ces 3 message a un effet différent, dépendant directe-ment de l’état dans lequel se trouve la pile.

Figure 1: Cycle de vie de l’objet "Pile"

2.2 TYPE ABSTRAIT: DÉFINITIONLa notion de type abstrait, très classique et très ancienne, est à la base du paradigme ob-jet. Nous verrons ainsi que les propriétés des types abstraits se rapprochent énormément de la notion d’objet présentée en début de chapitre.

Définition 2: Types abstraits

Un type abstrait définit une structure de données par un ensemble d’opérationsapplicables aux objets de ce type et une description sémantique de ces opérations.

Un type abstrait définit une «structure de données».. Prenons l’exemple du type abstrait «Rectangle». Un objet de type «Rectangle» sera caractérisé par 4 informations: les coordonnées «x» et «y» de son point supérieur gau-

Vide

estVide()création (new)

Non vide

empiler(élément)

estVide()

PleineestVide()

empiler(élément)

dépiler()

empiler(élément)/ Exception

destruction

dépiler()/ Exception

dépiler()

destruction

ELS - 3 février 2003

Page 16: Programmation Objet Avec Java

POO avec Java - 8 - Objets et types abstraits

che, sa largeur et sa hauteur.

Ces 4 informations seront variables: leur valeur pourra être modifiée.

Ces 4 informations, prises ensemble, constituent ce qu’on appelle une structure de don-nées.

Un type abstrait est défini par un «ensemble d’opérations»..

Définition 3: Opérations

Pour un type abstrait, on distingue en général trois catégories d’opérations:

1. les constructeurs; Ces opérations sont exécutées au moment de la création de l’objet. Elles servent à donner un état initial à l’objet: une valeur initiale aux différentes variables de la structure de données. Typiquement, on rencontrera 3 espèces de construc-teurs: le constructeur sans paramètre, qui donnera une valeur par défaut à l’objet, un ou plusieurs constructeurs avec paramètres et enfin un constructeur de copie qui crée un objet en copiant la valeur d’un objet passé en paramètre.

2. les modificateurs;Ecrits souvent sous la forme setXxx(..), ces opérations changent l’état de l’objet.

3. les observateurs (ou accesseurs), ou encore interrogateurs;Ecrits souvent sous la forme getXxx(..), ces opérations permettent «d’inter-roger» l’objet et retournent des informations concernant l’état de l’objet.

Par exemple, le type abstrait «Rectangle» peut être caractérisé par l’ensemble des opérations suivantes:

Tableau 7: Opérations du type «Rectangle»

Opération Catégorie Explication

new Rectangle () (1) Constructeur Création d’un nouveau rectan-gle.L’état initial de cet objet aura une valeur «par défaut», comme par exemple <0,0,0,0>.

new Rectangle (3,4, 20, 10)

(1) Constructeur Création d’un nouveau rectangle. Les 4 paramètres du constructeur permettent de spécifier une valeur initiale pour les coordonnées «x», «y» du point supérieur gauche, pour la largeur et la hauteur.

ELS - 3 février 2003

Page 17: Programmation Objet Avec Java

POO avec Java - 9 - Objets et types abstraits

Définition 4: Caractéristiques d’une opération

Une opération est définie par 3 éléments:

1. son nom; 2. la liste des paramètres, avec leur type; 3. le type de la valeur retournée par l’opération.

new Rectangle(Rectangle r)

(1) Constructeur Création d’un nouveau rectangle. Par copie du rectangle «r» (para-mètre)

agrandir (5) (2) Modificateur Pour agrandir le rectangle. La lar-geur et la hauteur du rectangle seront augmentées de la valeur «delta», définie dans le paramètre de l’opération

deplacer (3, 4) (2) Modificateur Pour déplacer le rectangle. Les deux paramètres servent à spécifier la valeur du déplacement en X, res-pectivement en Y

getLargeur (10) (2) Modificateur Pour spécifier la largeur du rectan-gle

setHauteur (20) (2) Modificateur Idem pour la hauteur

setX (5) (2) Modificateur Idem pour X

setY (4) (2) Modificateur Idem pour Y

getLargeur() (3) Accesseur Cette opération, sans paramètre, «retourne» la largeur du rectangle.

getHauteur() (3) Accesseur Idem, pour la hauteur.

getX() (3) Accesseur Idem pour X

getY() (3) Accesseur Idem, pour Y

contientLePoint(5, 6)

(3) Interrogateur Cette opération retourne «true», si le point, dont les coordonnées sont passées en paramètres, est inscrit dans le rectangle. Dans le cas con-traire, cette opération retourne «false».

Tableau 7: Opérations du type «Rectangle»

Opération Catégorie Explication

ELS - 3 février 2003

Page 18: Programmation Objet Avec Java

POO avec Java - 10 - Objets et types abstraits

A titre d’exemple, reprenons le type «Rectangle»..

Chaque paramètre est défini par son type. Dans notre exemple, il s’agit toujours du type «int» qui représente les entiers.

Notre exemple met en évidence les deux propriétés suivantes:

Propriété 1Les constructeurs et les modificateurs ne retournent aucune valeur. Le rôle de ces opérations se borne uniquement à modifier l’état de l’objet. En program-mation, ces opérations sont mises en œuvre par des procédures.

Tableau 8: Caractéristiques des opérations du type Rectangle, définies en UMLa

Nom de l’opération Paramètres Type

retourné Catégorie

new Rectangle (x: int, y: int,largeur: int, hauteur: int)

-- 1: Construc-teur

new Rectangle () -- pas de paramètre -- 1: "" ""

new Rectangle (r: Rectangle) -- 1: "" ""

agrandir (delta: int) -- 2:Modifica-teur

déplacer (deltaX: int, deltaY: int) -- 2: "" ""

setLargeur (l: int) -- 2: "" ""

setHauteur (h: int) -- 2: "" ""

setX (x: int) -- 2: "" ""

setY (y: int) -- 2: "" ""

getLargeur () -- pas de paramètre int 3: Observa-teur

getHauteur () -- pas de paramètre int 3: "" ""

getX () -- pas de paramètre int 3: "" ""

getY () -- pas de paramètre int 3: "" ""

contientLe-Point

(coordX: int, coordY: int) boolean 3: "" ""

a. UML est un langage de modélisation standard, indépendant des langages programmation

ELS - 3 février 2003

Page 19: Programmation Objet Avec Java

POO avec Java - 11 - Objets et types abstraits

Propriété 2 Les observateurs retournent toujours une valeur. Ces opérations se conten-tent d’interroger l’objet. Elles ne modifient pas l’état de l’objet. En programma-tion, objet, ces opérations sont mises en oeuvre par des fonctions.

Remarquons qu’en langage UML1, ces opérations seront définies de la manière sui-vante:

new Rectangle (x: int, y: int,largeur: int, hauteur: int)

new Rectangle ()

déplacer (deltaX: int, deltaY: int)

contientLePoint (coordX: int, cordY: int): boolean...

Notamment, on notera qu’en UML:

les parenthèses sont obligatoires, même s’il n’y a pas de paramètre;chaque paramètre obéit à la syntaxe: <nom>:<type>; le type retourné est indiqué à la fin: <type>

2.2.1 Définition du type abstrait «Rectangle» en JavaEn Java, un type abstrait peut être défini directement au moyen d’une classe [Voir para-graphe - Implémentation du type abstrait en Java - page 13], mais c’est la notion d’interface, propre à Java qui s’en rapproche le plus.

Programme 1: Le type abstrait: une interface

interface Rectangle_I {

// Modificateurspublic void agrandir(int delta);

public void déplacer (int deltaX, int deltaY);

// Observateurspublic int getX ();

public int getY ();

public int getLargeur();

public int getHauteur ();

public boolean contientPoint (int pX, int pY);}

1. UML est un langage de modélisation standard, indépendant des langages programmation

ELS - 3 février 2003

Page 20: Programmation Objet Avec Java

POO avec Java - 12 - Objets et types abstraits

Une interface, comme son nom l’indique, décrit simplement la liste des méthodes mises à disposition par le type abstrait, en spécifiant leur nom, et la manière de les utiliser (paramètres et type retourné).

☺ une interface pour déclarer des variables ou des paramètres de méthodes

Une interface est simplement une espèce de «mode d’emploi»: une fois définie, une interface pourra être utilisée pour déclarer des variables ou plus généralement des paramètres de méthodes qui désigneront des objets susceptibles de répondre aux opé-rations spécifiées dans l’interface.

Comme par exemple:

Rectangle_I r; // «r» est une variable.La variable «r» sera utilisée pour désigner un objet de type «Rectangle_I».A ce stade, il n’y a pas eu de création d’objet: cette variable vaut «null», elle ne désigne pour l’instant aucun objet.

:

:

public void uneMethode (Rectangle_I r) {De manière générale, les interfaces sont utilisées pour déclarer des par-amètres de méthodes. Dans le cadre de la procédure «uneMethode», «r» désignera un objet répondant exclusivement aux messages spécifiés dans l’interface «Rectangle_I»

/* code de la méthode «uneMethode» */

}

☺ une interface ne définit aucun constructeur

On remarquera qu’une interface ne donne aucune information au sujet des constructeurs potentiels.

Une interface ne permet pas de créer un objet. En effet, la création de l’objet dépendra directement de la manière dont sera «implémentée» l’interface et notamment de la struc-ture de données mise en oeuvre, il n’est donc pas prévu de constructeur à ce stade.

Notamment, lors de la création d’un objet, le système devra lui réserver une zone mémoire, qui contiendra sa valeur. La taille de cette zone mémoire et sa valeur dépend bien entendu de la manière dont sera implémentée la structure de données.

ELS - 3 février 2003

Page 21: Programmation Objet Avec Java

POO avec Java - 13 - Objets et types abstraits

Pour créer l’objet, il faudra réaliser l’implémentation de l’interface au moyen d’une classe. Ce que nous verrons dans le point suivant.

Et quand l’objet aura été créé, il sera possible de lui envoyer l’un ou l’autre des messa-ges spécifiés dans l’interface. Comme par exemple:

r.déplacer (4, 5);r.agrandir (4);int unEntier; // Déclaration d’une variable de type entier

unEntier= r.getLargeur(); // «unEntier» vaut la largeur// actuelle du rectangle

2.2.2 Implémentation du type abstrait en JavaL’interface décrit dans le programme [“Programme 1:”, page 11] permet de déclarer un ob-jet, mais pas de le créer. Pour créer un objet de type «Rectangle_I», il nous faut main-tenant réaliser le type abstrait. Dans le jargon informatique, on dira implémenter le type abstrait. En Java, on utilisera à cette fin le mécanisme de classe.

Programme 2: Implémentation de l’interface

class Rectangle implements Rectangle_I{

// Variables d’instanceprivate int x; // Point supérieur gauche

private int y;private int lg; // Largeur et Hauteur

private int ht;

// Constructeur (s)public Rectangle () {

x = y = 0;lg = ht = 0;

}

public Rectangle (Rectangle r) {x = r.x; y = r.y; lg = r.lg; ht = r.ht;// ou: this (r.x, r.y, r.lg, r.ht);// utilisation du constructeur avec paramètres

}

public Rectangle(int pX, int pY, int largeur,int hauteur) {x = pX; y = pY;lg = largeur; ht = hauteur;

}

// Modificateurs

public void agrandir(int delta) {lg = lg + delta;ht = ht + delta;

}

ELS - 3 février 2003

Page 22: Programmation Objet Avec Java

POO avec Java - 14 - Objets et types abstraits

public void déplacer (int deltaX, int deltaY) {x = x + deltaX;y = y + deltaY;

}

public void setX(int x) {this.x = x;}public void setY(int y) {this.y = y;}public void setLargeur(int lg) {this.lg = lg;}public void setHauteur(int ht) {this.ht = ht;}

// Observateurs

public int getX () {return x;}public int getY () {return y;}public int getLargeur() {return lg;}public int getHauteur () {return ht;}

public boolean contientPoint (int pX, int pY) {return (pX > x) && (pX < x+lg) && (pY > y) &&

(pY < y+ht);}

}

Une classe qui implémente une interface a la particularité:

1. d’une part, de rappeler la définition du type abstrait en donnant la spécification des opérations applicables aux objets:

void agrandir(int delta)void déplacer (int deltaX, int deltaY)int getX()int getY ()...

2. d’autre part, de réaliser le type abstrait en proposant une représentation de la structure de données au moyen des variables d’instance:

int x;int y;int lg;int ht;

3. et enfin, de terminer la réalisation du type abstrait en décrivant les instructions accomplies par chacune des opérations à l’intérieur d’une paire d’accolades {..}:

void déplacer (int deltaX, int deltaY) {x = x + deltaX;y = y + deltaY;

}

ELS - 3 février 2003

Page 23: Programmation Objet Avec Java

POO avec Java - 15 - Objets et types abstraits

Nous pouvons maintenant créer véritablement un objet de type «Rectangle_I», la classe «Rectangle» a prévu à cet effet trois constructeurs:

Programme 3: Création d’objets

Rectangle_I r1, r2, r3;Déclaration de 3 variables

r1 = new Rectangle ();Création de l’objet en utilisant le constructeur sans paramètre: la lar-geur, la hauteur ainsi que les coordonnées de son point supérieur gau-che valent respectivement <0, 0, 0, 0>

r2 = new Rectangle (3, 4, 20, 10);Création de l’objet en utilisant le constructeur avec paramètres

r3 = new Rectangle (r2);Création de l’objet en utilisant le constructeur de copie: la largeur, la hauteur ainsi que les coordonnées de son point supérieur gauche valent respectivement <3, 4, 20, 10>

2.2.3 Création de l’objet: l’opérateur «new»Nous explicitons ici le fonctionnement de l’opérateur new du langage Java.

Soit: r = new Rectangle (3, 4, 20, 10) ;

La création de l’objet r s’effectue en 3 étapes :

1. Allocation d'une zone mémoire, avec initialisationPar exemple :

private int x ;// "x" est initialisée à 0 (par défaut)

private int y = 3 ;// "y" est initialisée avec 3

2. Invocation du constructeur (Rectangle(..))De nouvelles valeurs peuvent être affectées aux variables d'instance.

3. Retour de la référenceL’opérateur new retourne pour finir la référence à l’objet, affectée à la variable r.

2.2.4 Destruction de l’objet: le message «finalize()»La destructiono de l’objet est opérée par le ramasse-miettes (garbage collector). Cette opération est susceptible d’arriver dès l’instant où l’objet n’est plus référencé par aucune variable.

ELS - 3 février 2003

Page 24: Programmation Objet Avec Java

POO avec Java - 16 - Objets et types abstraits

Les dernières volontés de l’objet..Avant même de détruire l’objet, le garbage collector lui envoie le message finali-ze(), lui donnant ainsi l’occasion d’accomplir ses dernières volontés.

Par défaut, la méthode invoquée, - définie dans la classe Object -, ne fait rien, mais le programmeur peut la redéfinir ainsi :

protected void finalize () throws Throwable {

/* ..Ecrire ici les dernières volontés de l’objet..*/

super.finalize();

// «If you override finalize, your implementation of the// method should call super.finalize as the last thing it// does»

}

Note d’utilisationL’utilisation de finalize(), qui joue le même rôle que le destructeur de C++, est ef-fectuée très rarement en Java, du fait de l’existence même du garbage collector.

2.2.5 Structure d’une classe JavaLa déclaration d'une classe obéit à la syntaxe suivante :

[Modificateurs de classe] class NomDeLaClasse

[extends nomDeLaSuperclasse]

[implements interface1, interface2,..]{

[Variables]

[Constructeurs]

[Méthodes]

}

☺ Notons que les variables, constructeurs et méthodes peuvent être déclarés dans n’importe quel ordre ! Par convention, on préfère commencer par déclarer les variables, puis les constructeurs, pour finir par les méthodes.

[Modificateurs de classe]

public classe visible depuis un autre paquetage1

1. Un paquetage est un ensemble de classes, regroupées concrètement dans un seul et même répertoire, portant lui-même le nom du paquetage.

ELS - 3 février 2003

Page 25: Programmation Objet Avec Java

POO avec Java - 17 - Objets et types abstraits

abstract classes abstraites [Voir paragraphe - Méthodes et classes abstraites - page 87]final classes non dérivables (héritage interdit)

[extends nomDeLaSuperclasse]Héritage simple[implements interface1, interface2,..]Implémentation d’interfaces

2.2.6 Encapsulation de la structure d’informationsOn remarquera que les différents éléments qui composent la classe «Rectangle» sont précédés par le mot-clé «public», ou encore par le mot-clé «private».

Les éléments «private» ont trait à la réalisation du type abstrait, et ne sont pas accessibles à l’extérieur de la classe.Les éléments «public» sont au contraire accessibles par des méthodes situées à l’extérieur de la classe: il s’agit notamment des opérations qui pourront être accomplies sur les objets de type «Rectangle_I» et invoquées depuis l’exté-rieur.

Définition 5: Encapsulation

L’encapsulation consiste à «cacher» de l’extérieur l’implémentation d’une structure d’informations. Disons plus exactement que l’encapsulation de la structure d’informa-tions décrite à l’intérieur du type abstrait ou de la classe, est un mécanisme qui rend cette structure inaccessible depuis l’extérieur.

Son accès se fera uniquement par le biais des opérations qui lui sont applicables, c’est-à-dire par les méthodes déclarées au sein de la classe et qui doivent, en revanche, être accessibles de l’extérieur (donc déclarées avec le mot-clé public)

// Variables d'instanceprivate int x, y;private int lg,ht;

// Constructeur (s)public Rectangle () {

x = y = 0;lg = ht = 0;

}

Rectangle()

Rectangle(int x, ..)

Rectangle(Rectangle r)

agrandir (int delta)

..

x

y

lg

ht

ELS - 3 février 2003

Page 26: Programmation Objet Avec Java

POO avec Java - 18 - Objets et types abstraits

Pourquoi encapsuler la structure d’information, dans quel but ? Nous renvoyons le lec-teur en fin de chapitre [Voir paragraphe - Intérêt des types abstraits - page 42].

2.2.7 Un type abstrait peut être défini directement par une classe

Définition 6: Une classe définit un type de données

Il n’est pas nécessaire en Java de passer par la notion d’interface: en donnant la liste de toute les méthodes applicables au type d’objet, une classe définit un type de don-nées. Il est donc possible de déclarer une variable à partir d’une classe.

En effet, comme le montre le programme 2, une classe a deux facettes: elle définit les opérations et elle les réalise. Comme elle définit les opérations, on peut l’utiliser pour déclarer des variables. Ainsi, la variable «r» pourrait être déclarée directement à partir de la classe «Rectangle»:

Rectangle r; // Déclaration à partir de la classe

r = new Rectangle ();

2.2.8 Type courant et type déclaréLe lecteur aura remarqué que l’opérateur «new», invoqué pour créer l’objet, a été appli-qué à une classe, qui ne correspond pas forcément au type utilisé pour déclarer la varia-ble:

Rectangle_I r = new Rectangle ();<nom-du-type> <nom-variable> = new <nom-de-la-classe>

Définition 7: Type courant et type déclaré

En programmation objet, on distinguera très nettement les notions de type courant et de type déclaré.

le type déclaré correspond au type qui a été utilisé pour déclarer la variable;le type courant correspond au type utilisé pour créer l’objet désigné par la variable.

Le type déclaré est connu par le compilateur: il spécifie la liste des opérations pouvant être appliquées à l’objet. Le compilateur vérifie que c’est bien le cas. Il refusera le pro-gramme si les propriétés du type déclaré ne sont pas respectées.

☺ En Java, le type déclaré peut être une classe ou bien encore une interface.

☺ Le type courant d’un objet correspond toujours à une classe.

Cette classe met à disposition les constructeurs qui auront permis de créer l’objet.

ELS - 3 février 2003

Page 27: Programmation Objet Avec Java

POO avec Java - 19 - Objets et types abstraits

Comme l’invocation de l’opérateur «new» peut être opérée n’importe où et n’importe quand dans le programme, c’est seulement à l’exécution que sera connu le type cou-rant de l’objet. Le compilateur ne connaît que le type déclaré.

La classe du type courant peut être la même que celle qui a été utilisée pour déclarer la variable: c’est le cas le plus usuel.

Il est possible toutefois que cette classe soit différente. Dans ce cas, il s’agira:

Soit d’une classe implémentant l’interface utilisé pour déclarer la variable;Soit d’une sous-classe de la classe utilisée pour déclarer la variable. Une sous-classe est une classe «héritière»: le polymorphisme et l’héritage, concepts pro-pre à la programmation objet et que nous étudierons dans un prochain chapitre [Voir paragraphe - Java et le mécanisme d’héritage - page 54], impliquent que le type courant d’un objet ne soit connu qu’au moment de l’exécution du programme.

Rectangle_I r1 = new Rectangle (3,4,20,10);

Rectangle r2 = new Rectangle (3,4,20,10);

Type courant = type de l'objetLe type courant est dynamique, connuuniquement à l'exécution

Type déclaré = type de la variableLe type déclaré définit l'ensemble des opérations qui peuvent être accomplies sur la variable.Le compilateur vérifie que cette règle est respectée

ELS - 3 février 2003

Page 28: Programmation Objet Avec Java

POO avec Java - 20 - Objets et types abstraits

Voici un programme Java qui utilise la classe «Rectangle»:

2.2.9 Interface ou classe ?Dans une première approche, nous avons utilisé le mécanisme d’interface en raison du fait que c’est ce dernier qui, en Java, s’apparente le plus au concept de type abstrait. Mais nous avons constaté que le mécanisme de classe convient parfaitement pour déclarer les types abstraits, si ce n’est qu’on mélange alors la définition du type d’une part, et sa réa-lisation d’autre part.

Dans 95% des cas, en pratique, c’est le mécanisme de classe qui sera utilisé. Ceci évi-tera bien des lourdeurs.

Par exemple, typiquement, le type «Rectangle» serait déclaré ainsi:

Programme 4: Une classe qui n’implémente aucune interface

class Rectangle {// Cette classe n’implémente aucun interface

private int x; // Point supérieur gauche

private int y;

private int lg; // Largeur et Hauteur

private int ht;

Textes affichés

Rectangle_I r; // Déclaration d’une variabler = new Rectangle (3, 4, 10, 20);

r.déplacer (2, 2);System.out.println

("Largeur: " + r1.getLargeur());System.out.println ("X: " + r1.getX());

Largeur: 10X: 5

r1.agrandir (3);System.out.println ("Largeur: " + r1.getLargeur());System.out.println ("X: " + r1.getX());

Largeur: 13

X: 5

if (r1.contientPoint (10, 10)) {System.out.println ("Contient <10, 10>");

}else {

System.out.println("Ne contient pas <10, 10>");

}

Contient <10, 10>

ELS - 3 février 2003

Page 29: Programmation Objet Avec Java

POO avec Java - 21 - Objets et types abstraits

public Rectangle () { // Constructeur

x = y = 0;lg = ht = 0;

}::}

Si les classes suffisent, pourquoi utiliser des interfaces ?

La notion d’interface est un concept spécifique à Java, et vient palier au fait que Java, contrairement à C++, ne connaît pas l’héritage multiple. En revanche, la notion d’inter-face offre à Java la possibilité d’opérer le typage multiple: le type d’une variable peut être multiple !

A titre d’exemple, considérons les déclarations suivantes..

Programme 5: Le typage multiple

interface Rectangle-I {/* identique au programme [“Programme 1:”, page 11]*/

}

interface ObjetGraphique {public void dessiner();

}

class RectangleA implements Rectangle_I {// implémentation de l’interface Rectrangle_I

:: voir [“Programme 2:”, page 13]:

// méthodes propres à la classe RectangleApublic void dessiner() {

/* instructions..*/

}public void effacer() {

/* instructions.. */

}}

class RectangleB {// Pour des rectangle de taille et de position non modifiables

private int x, y, lg, ht;

public RectangleB (int x, int y, int lg, int ht) {this.x = x; this.y = y; this.lg = lg; this.ht = ht;

}

ELS - 3 février 2003

Page 30: Programmation Objet Avec Java

POO avec Java - 22 - Objets et types abstraits

public int getX() {return x;}public int getY() {return y;}public int getLargeur() {return lg;}public int getHauteur() {return ht;}

}

class Fenetre implements Rectangle_I, ObjetGraphique {// Implémentation de l’interface Rectangle_I

:: Implémentation de toutes les méthodes de l’interface :

// Implémentation de l’interface ObjetGraphiquepublic void dessiner() { /* instructions */ }

// Méthode propre à la classe Fenetrepublic void effacer() { /* instructions */ }

}

Considérons maintenant quelques déclarations de variables:

Rectangle_I ri = new RectangleA();RectangleAra = new RectangleA();RectangleB rb = new RectangleB (2, 3, 4,5);ObjetGraphique og = new Fenetre();Fenetre f = new Fenetre();

Parmi ces variables, certaines ont des propriétés qui appartiennent à plusieurs types de données. Ainsi, suivant le rôle que joue chacune d’entre elles à un moment donné du programme, - et ce rôle peut varier, tout comme les individus peuvent jouer plusieurs rôles au sein de la société -, les opérations qui leur seront appliquées appartiendront tantôt à un type de données X, tantôt à un type de données Y. Ainsi, l’objet f «est-une» Fenetre. Mais on peut dire aussi que f «est-un» ObjetGraphique, ou que f «est-un» Rectangle_I.

Voici les types de données caractérisant chacune de ces variables:

Grâce à la notion de typage multiple, il est possible de réaliser des méthodes dont les paramètres, s’ils sont de type interface, sont susceptibles de prendre des formes extrê-mement variées.

ri Rectangle_I

ra Rectangle_I,RectangleA

rb RectangleB

og ObjetGraphique

f ObjetGraphiqueRectangle_IFenetre

ELS - 3 février 2003

Page 31: Programmation Objet Avec Java

POO avec Java - 23 - Objets et types abstraits

Ainsi, si nous considérons les deux méthodes décrites ci-dessous:

public void m1 (ObjetGraphique o) {

:

o.dessiner();

:

}public void m2 (Rectangle_I r) {

:r.déplacer(3);:

}

Les invocations suivantes sont alors correctes. En effet, les paramètres effectifs utilisés ont chacun au moins un type (parmi les types multiples qui les caractérisent) qui corres-pondant au type du paramètre formel.

m1(f);m1(og);m2(ra);m2(ri);

2.2.10Une interface pour déclarer des constantes de configurationEn règle générale, les interfaces sont utilisées pour définir des liste d’opérations. Il est toutefois possible d’y définir des constantes symboliques comme par exemple la cons-tante PI qui serait déclarées de la manière suivante:

public static final double PI = 3.14156;

Les constantes symboliques sont déclarées comme des variables (eh oui !) qui compor-tent une valeur d’initialisation non modifiable.

Il suffit donc de déclarer une variable en précisant qu’elle est non modifiable au moyen du mot-clé final.

☺ Par convention d’écriture, les constantes symboliques sont écrites en majuscules

Les constantes de configuration d’un programme peuvent être rassemblées dans une interface comme ci-dessous (pour un programme de jeu):

interface Configuration {// Déclaration des constantes de configuration, accessibles// globalement par tout le programme

public static final int NOMBRE_MAX_JOUEURS = 20;public static final int NOMBRE_MAX_ESSAIS = 3;

}

☺ Remarque: dans une interface, les mot-clés public, final et static sont implicites et n’ont pas besoin d’être écrits

ELS - 3 février 2003

Page 32: Programmation Objet Avec Java

POO avec Java - 24 - Objets et types abstraits

Les classes qui désirent utiliser ces constantes doivent alors le déclarer explicitement au moyen d’une implémentation. Par ce biais, les constantes en question deviennent alors directement accessibles, comme par exemple:

class Jeu implements Configuration {:public void run {

while (NoEssai <= NOMBRE_MAX_ESSAIS) {::

}}

}

☺ Le fait de devoir opérer une implémentation spécifique de l’interface est inté-ressant du point de vue de la lisibilité du programme puisque que l’on peut repérer assez facilement toutes les classes dépendantes de la valeur des constantes de configu-ration.

2.3 SÉMANTIQUE DES OPÉRATIONSJusqu’à présent, nous avons présenté les opérations des types abstraits uniquement sous un angle «syntaxique». C’est-à-dire que nous nous sommes préoccupé des règles à sui-vre pour écrire une opération: écrire le nom de l’opération, puis ouvrir une parenthèse, écrire le ou les paramètres éventuels, etc..

Définition 8: Sémantique des opérations

La sémantique est à la syntaxe ce que le «fond» et à la «forme». La sémantique des opérations s’intéresse aux propriétés des opérations, elle s’intéresse particulièrement:

à ce que fait l’opération, si cette dernière est utilisée correctement; aux conditions à respecter pour utiliser correctement l’opération.

Les conditions à respecter constituent ce qu’on appelle les préconditions.

Les postconditions représentent le résultat de l’exécution de l’opération.

Définition 9: Préconditions

Les préconditions sont des conditions à respecter:

sur la valeur de l’objet qui doit subir l’opération,sur la valeur des paramètres de l’opération. Si ces conditions ne sont pas res-pectées, l’opération n’a pas de sens: elle ne doit pas être appliquée. Si malgré tout, cette opération était appliquée, son résultat serait tout à fait indéfini. En d’autre termes, le programme «planterait».

ELS - 3 février 2003

Page 33: Programmation Objet Avec Java

POO avec Java - 25 - Objets et types abstraits

Définition 10: Postconditions

Les postconditions sont des faits qui sont toujours vrais après l’exécution de l’opéra-tion, à condition évidemment que les préconditions aient été respectées.

Ces faits concernent la nouvelle valeur de l’objet.

En gros, les postconditions décrivent la fonction même, réalisée par l’opération.

Intéressons-nous maintenant à la manière de décrire les préconditions et les postcondi-tions associées à une opération.

2.3.1 Description informelle de la sémantiqueDans certains cas, un texte descriptif peut suffire. On parle alors de description informel-le.

Considérons par exemple une «description informelle» (textuelle) de l’opération «agrandir».

PRECONDITIONS [agrandir(delta)] -->On peut augmenter la largeur et la hauteur d’un rectangle d’une valeur «delta» (delta pouvant être négatif), à condition que la largeur et la hauteur du rectangle qui en résulte restent positives ou nulles. Si «delta» est négatif, cela entraînera une diminution du rectangle.

POSTCONDITIONS [agrandir(delta)] -->Après son agrandissement, la largeur et la hauteur du rectangle sont égales à leur ancienne valeur, augmentée de la valeur delta, passée en paramètre. Les coordonnées du point supérieur gauche du rectangle sont inchangées.

On peut vérifier en analysant ces préconditions et postconditions que ces dernières sont bien exprimées en fonction:

1. de l’état de l’objet (sa valeur) avant et après l’opération; 2. de la valeur des paramètres.

2.3.2 Description formelle de la sémantiqueUne description textuelle est parfois ambiguë, peu précise et souvent très lourde.

Dans la mesure du possible, on préfère décrire la sémantique au moyen «d’expressions mathématiques». C’est ce que l’on appelle une «description formelle».

La notion «d’expression»Une «expression» est une formule mathématique qui peut être «évaluée», c’est-à-dire

ELS - 3 février 2003

Page 34: Programmation Objet Avec Java

POO avec Java - 26 - Objets et types abstraits

qui «retourne» un résultat si on demande son exécution.

Voici quelques exemples d’expressions, où «r» est un objet de type «Rectangle_I». On supposera que la largeur et la hauteur du rectangle valent respectivement «5» et «10» et que son point supérieur gauche est positionné en <3, 4>.

Tableau 9: Exemples d’expressions

Expression Résultat de l’expression

1/ r.getLargeur() 5getLargeur() est une fonction. Le résultat de l’expression correspond à la valeur retournée par la fonc-tion.

2/ r.getX() 3

3/ r.getY() 4

4/ r.agrandir(2) «r» (l’objet qui subit l’opéra-tion).En effet, agrandir(..) est une procédure. L’expression retourne alors l’objet même qui reçoit le messagea.

5/ r.getLargeur() 7 (le rectangle a été agrandi)

6/ r.agrandir(4).getLargeur() 11Enchaînement de messages de gauche à droite. Cette expression retourne la valeur de la largeur du rectangle après un nouvel agrandisse-mentb.

7/ new Rectangle() Un nouvel objet de type «Rectangle». Valeur du point supérieur gauche: <0,0>, largeur: <0>, hau-teur: <0>

8/ r = new Rectangle() «r» (un nouveau rectangle de valeur <0,0,0,0>)Une affectation («=») est une expression qui retourne l’entité qui a été affectée.

ELS - 3 février 2003

Page 35: Programmation Objet Avec Java

POO avec Java - 27 - Objets et types abstraits

Une remarque importante ! !

Les expressions écrites ci-dessus ne sont valables que dans le contexte de la sémantique des opérations.

Ecrites en Java, les expressions no 4 et no 6 ne seraient pas valables puisqu’une procé-dure Java ne retourne rien.

Ecrite en Java, l’expression no 4 n’en est pas une ! En effet, «agrandir» est une pro-cédure et par conséquent, «c.agrandir(2)» ne retourne absolument rien.

En Java, toujours, l’expression no 6 n’est pas correcte non plus, et pour les mêmes rai-sons. Comme «c.agrandir(4)» ne retourne rien, on ne peut pas envoyer le message «getLargeur()» à ce «rien».

Exprimer la sémantique des opérationsRevenons maintenant aux opérations. Pour ce qui nous concerne, nous décrirons la sé-mantique au moyen d’axiomes, des expressions logiques qui doivent toujours être «vraies».

Pour exprimer la sémantique d’une opération, les préconditions utiliseront le mot-clé «this», qui désigne l’objet qui doit subir l’opération. L’état de cet objet est celui qu’il possède avant de subir l’opération.

Pour plus de clarté, nous utiliserons dans le cadre des postconditions le mot-clé «old», qui désignera l’objet dans son état avant l’opération, et le mot-clé «new» qui désignera l’objet après avoir subit l’opération.

Reprenons par exemple l’opération «agrandir (delta)».Voici une description formelle de sa sémantique:

PRECONDITIONS [agrandir(delta)]

this.getLargeur() + delta >= 0this.getHauteur() + delta >= 0

9/ (r = new Rectangle()).getLargeur() 0 (largeur de «r», un nou-veau rectangle)

a. Ceci serait valable également en Smalltalk, mais pas en Java: une procédure ne retourne rien en Java ! !

b. Ceci serait valable aussi en Smalltalk, mais pas en Java: «c.agrandir ()» ne retourne rien («agrandir» est une procédure), et on ne peut donc pas enchaîner avec le message «getLargeur()»

Tableau 9: Exemples d’expressions

Expression Résultat de l’expression

ELS - 3 février 2003

Page 36: Programmation Objet Avec Java

POO avec Java - 28 - Objets et types abstraits

POSTCONDITIONS [agrandir(delta)]

new.getLargeur() == old.getLargeur() + deltanew.getHauteur() == old.getHauteur() + deltanew.getX() == old.getX() new.getY() == old.getY()

Considérons maintenant l’exemple de l’opération «new Rectangle()». PRECONDITIONS [new Rectangle()]

-- (pas de précondition)

POSTCONDITIONS [new Rectangle()]

new.getLargeur() == 0new.getHauteur() == 0new.getX() == 0new.getY() == 0

2.3.3 La programmation «par contrat»Bertrand Meyer, informaticien de renom et concepteur du langage Eiffel, a développé voici quelques années le concept de programmation par contrat.

Ce concept a pour but de simplifier le raisonnement du programmeur qui doit conce-voir une nouvelle opération, et également le raisonnement de celui qui, au contraire, doit utiliser une opération écrite par un autre, ou éventuellement par lui-même.

L’idée est relativement simple. Une opération est un «service». Demander l’exécution d’une opération peut être imaginée comme la signature d’un contrat entre une entre-prise qui offre ce service et nous mêmes qui en sommes le client.

PostconditionsL’entreprise s’engage à remplir son contrat et à accomplir le service demandé.PréconditionsEn revanche, le client doit assurer que les conditions préalables sont respectées.

☺ Donc,..

Avant d’appeler une opération, le programmeur-client doit s’assurer que les préconditions sont respectées. Il lèvera une exception si ça n’est pas le cas.Notons que dans certains styles de programmation, la levée d’exception est plu-tôt opérée en début d’opération par le programmeur-entreprise lui-même (qui contrôle si les préconditions sont respectées)Pour s’assurer que le contrat est rempli, le programmeur-entreprise doit s’assu-rer que les postconditions sont respectées (sinon, le programmeur-entrepriselèvera une exception en fin d’opération).Le programmeur-entreprise n’a pas à prévoir de comportement par défautsi jamais les préconditions n’ont pas été respectées. L’opération doit se concen-trer sur une seule chose: accomplir sa tâche correctement.

ELS - 3 février 2003

Page 37: Programmation Objet Avec Java

POO avec Java - 29 - Objets et types abstraits

Une fois l’opération terminée, le programmeur-client est assuré que l’opération s’est correctement déroulée: c’est dans le contrat, il n’a pas à le vérifier (c’est-à-dire à vérifier les postconditions).

Toute rupture de contrat (préconditions ou postconditions non respectées) est considérée comme une erreur de programmation, et doit avoir pour effet de «planter» le programme à moyen terme.

Ainsi, une opération devrait obéir théoriquement au schéma suivant:

public void uneOperation(paramètres) {1/ Traitement de l’opération, supposant que les préconditions

sont respectées par le programmeur-client2/ Contrôle des postconditions, avec levée

d’exception si ces dernières ne sont pas respectées}

OU

public void uneOperation(paramètres) {1/ Contrôle des préconditions, avec levée

d’exception si ces dernières ne sont pas respectées2/ Traitement de l’opération3/ Contrôle des postconditions, avec levée

d’exception si ces dernières ne sont pas respectées}

Moyens mis à disposition en Java: instruction «assert»Introduite avec la version 1.4, l’instruction assert obéit à la syntaxe de base suivante:

assert expressionBooléenne;

ou: assert expressionBooléenne: MessageString;

Principe de fonctionnementSi les assertions sont désactivées, l’instruction assert n’a pas d’effet. Si au contraire les assertions sont activées (comme par exemple en phase de tests unitaires du program-me), l’erreur AssertionError est levée si l’expression booléenne est évaluée à false(avec affichage de l’éventuel MessageString).

Pour compiler (depuis la version 1.4.x)

Ne pas oublier le flag «-source 1.4» !!

> javac -source 1.4 Toto.java

En cas d’oubli, le compilateur considère que le mot-clé assert est un mot-clé défini par le programmeur plutôt qu’un mot-clé appartenant au langage.

ELS - 3 février 2003

Page 38: Programmation Objet Avec Java

POO avec Java - 30 - Objets et types abstraits

Activation et désactivation des assertions

Par défaut, les assertions sont désactivées. Pour les activer:

> java -enableassertions Toto

ou: > java -ea Toto

Un exemple de programmation par contrat avec l’opération «agrandir»Par exemple, considérons l’opération «agrandir», écrite et utilisée dans un contexte de programmation par contrat. L’exemple est écrit en Java.

Premier style de programmationDéfinition de l’opération par le programmeur-entreprise (les postconditions sont rem-plies implicitement):

void agrandir (int delta) {lg = lg + delta;ht = ht + delta;

}

Son utilisation par le programmeur-client, avec levée d’exception si les préconditions ne sont pas remplies:

assert (unRectangle.getLargeur() > 10 &&unRectangle.getHauteur() > 10): "CestMalBarre";

unRectangle.agrandir(-10);

Deuxième style de programmationDéfinition de l’opération par le programmeur-entreprise qui lève une exception si les préconditions ne sont pas respectées par le client. Les postconditions sont remplies im-plicitement.

void agrandir (int delta) {assert (unRectangle.getLargeur() > delta &&unRectangle.getHauteur() > delta): "CestPasSerieux";lg = lg + delta;ht = ht + delta;

}

ELS - 3 février 2003

Page 39: Programmation Objet Avec Java

POO avec Java - 31 - Objets et types abstraits

Son utilisation par le programmeur-client, qui s’assure auparavent que les précondi-tions sont respectées:

unRectangle.agrandir(-10);

A titre de comparaison, voici la même opération (celle du programmeur-entreprise), écrite en utilisant cette fois-ci le mécanisme classique de levée d’exceptions. C’est évi-demment moins efficace que les assertions: le test de contrôle est en effet toujours activé, même si, une fois le programme testé, on est sûr que l’exception ne sera jamais levée. .

void agrandir (int delta) {if (unRectangle.getLargeur() > delta &&unRectangle.getHauteur() > delta))

throw new Exception("CestPasSerieux");lg = lg + delta;ht = ht + delta;

}

Un exemple de programmation «redondante», - voire dangereuse -, chez le programmeur-entreprise.

void agrandir (int delta) {lg = lg + delta;ht = ht + delta;if (lg < 0) { // Ici, le programmeur se complique la vie,

lg = 0; // il suppose que les préconditions n’ont

} // pas été respectées par le client,: // et prévoit un comportement par défaut

}

Toute la difficulté réside dans l’établissement du contrat, c’est-à-dire dans la définition des préconditions et des postconditions. Il est possible par exemple que l’opération pré-voie des «comportements par défaut», mais ça devrait être prévu dans le contrat, dans le cadre des postconditions.

2.4 DÉFINIR LA STRUCTURE DE DONNÉES: LES TYPES DE JAVA

Un type abstrait définit une structure de données par un ensemble d’opérations..

ELS - 3 février 2003

Page 40: Programmation Objet Avec Java

POO avec Java - 32 - Objets et types abstraits

Après nous être attardés sur les opérations, intéressons-nous plus particulièrement à la définition de la structure de données elle-même et à la manière de la mettre en oeuvre en Java.

Concrètement, cette structure de données sera décrite en termes de variables dont le type devra être mentionné obligatoirement.

En préambule, deux remarques importantes:

Java est fortement typé: dans une même expression, les types doivent être identiques Il existe deux catégories de types: les types primitifs et les types objets

2.4.1 Les types primitifsCes derniers constituent les briques de base du langage. Ils sont construits à partir d'aucun autre type.

Tableau 10: Liste des types primitifs

bouléens boolean true, false

caractères char caractères 16-bits, Unicode

entiers byte 8-bits, entiers signésshort 16-bits, entiers signésint 32-bits, entiers signés

type par défaut des constantesentières

long 64-bits, entiers signés

réels float 32 bits, virg. flottante, simple précision(IEEE 754-1985)

double 64 bits, virgule flottante, double précision(IEEE 754-1985)

type par défaut des constantesréelles

Valeur par défautJava leur garantit une valeur par défaut: 0 (false pour les booléens, le caractère de code 0 pour les caractères).

Transtypage, coercition de typeComme Java est un langage fortement typé, des conversions de type sont parfois néces-saires, ce que l’on appelle encore «coercition de type» ou «transtypage».

ELS - 3 février 2003

Page 41: Programmation Objet Avec Java

POO avec Java - 33 - Objets et types abstraits

La coercition de type doit obligatoirement être spécifiée par le programmeur lorsque la conversion entraîne une perte de précision ou une perte de capacité, comme de passer d’un réel à un entier, ou de passer d’un «long» (64 bits) à un «int» (32 bits).

Dans le cas contraire (qui ne présente aucun danger), la coercition est implicite.

Comme par exemple:

double d = 3.5;int x = (int)d; // Coercition obligatoire.

short s = (short)3; // Coercition obligatoire

// («3» est de type «int»)

float f = x; // Coercition implicite

f = (float)3.5; // Coercition obligatoire

// («3.5» est de type «double»)

Les classes «wrappers»Comme il est parfois nécessaire de manipuler les primitifs comme des objets, le paque-tage java.lang met à disposition des classes «wrappers», prévues spécifiquement pour encapsuler des primitifs.

Un exemple d’utilisation:

Integer ii = new Integer(3); // Encapsulation du primitif «3»

::int i = ii.intValue(); // Récupération du primitif

Tableau 11: les classes Wrappers

primitif classe Wrapper

int Integer

short Short

byte Byte

long Long

float Float

double Double

boolean Boolean

char Character

ELS - 3 février 2003

Page 42: Programmation Objet Avec Java

POO avec Java - 34 - Objets et types abstraits

Quel intérêt ?Pouvoir utiliser des primitifs partout où des objets sont exigés, comme par exemple in-sérer des primitifs dans une liste dynamique de type Vector:

La classe Vector propose en effet la méthode addElement (unElement) qui per-met d’insérer l’élément «unElement» en queue de liste. Sa définition est la suivante:

public void addElement(Object o);

L’insertion du primitif «3» pourra se faire ainsi:

Vector unVector = new Vector();::unVector.addElement(new Integer(3));

Récupération de l’élément:

Integer ii = (Integer)(unVector.lastElement());

i = ii.intValue();

Le transtypage «(Integer)» est obligatoire car la fonction lastElement retourne une valeur de type général Object, qui sera affectée à une variable de type Integer.

public Object lastElement();

Notons que cette coercition n’opère aucune conversion. Il s’agit simplement d’un signal donné au compilateur pour lui indiquer que le programmeur est responsable, et sait ce qu’il fait..

2.4.2 Les types «objets»Cette catégorie comprend les instances de classe et les tableaux («arrays») .

les objets sont créés dynamiquement au moyen de l'opérateur «new». A la dif-férence des primitifs, les objets sont manipulés par référence, comme on le verra dans le paragraphe suivant.

leur destruction est automatique : automatiquement récupérés par le « ramasse-miettes » (le «garbage collector») quand ils ne sont plus référencés.

Valeur par défautPar défaut, une variable de type objet prend la valeur «null».

JFrame maFenetre; // == null

maFenetre = new JFrame(); // L’objet est créé, la// variable contient alors// la référence sur l’objet.

ELS - 3 février 2003

Page 43: Programmation Objet Avec Java

POO avec Java - 35 - Objets et types abstraits

2.4.3 Partage d’objets, copie superficielle et copie profonde

Les primitifs sont manipulés par valeur alors que les objets sont manipulés par référence !

Voici illustré la différence de principe entre une variable x de type primitif et une variable x de type objet:

Les primitifs sont manipulés par valeur

3x

x

int x = 3;

// x est un "type primitif",// x contient la valeur "3"

Point x = new Point (2, 3);

// x est un type objet// x est une référence sur l'objet

2 3

un point

L’affectation de types primitifs opère une copie de valeur ;

Ainsi, les valeurs des deux variables x et y restent indépendantes l’une de l’autre

0x 0yint x, y;

x = 3; 3x 0y

y = x; 3x 3y

y = 5; 3x 5y

ELS - 3 février 2003

Page 44: Programmation Objet Avec Java

POO avec Java - 36 - Objets et types abstraits

Les objets sont manipulés par référence

Copie d’objetsL’affectation de variables opère un partage d’objets, la copie d’objets à proprement dite nécessite l’écriture de méthodes spécifiques.

Il faut alors distinguer la copie superficielle («shallow copy»), de la copie profonde(«deep copy») qui effectue également une copie des sous-objets de manière récursive.

Le programmeur doit déterminer lui-même le type de copie qu’il désire mettre à dispo-sition.

A titre d’exemple, les classes «MyPoint» et «MyRectangle», présentées ci-après, proposent chacune une méthode de copie profonde. La classe MyRectangle met éga-lement à disposition une procédure de copie superficielle.

L’affectation d’objets opère un partaged’objets : le même objet est référencé par deux variables diffé-rentes.

Par la suite, la valeur de l’objet peut être modifiée en passant par l’une ou l’autre des deux variables

p1Point p1, p2; p2

p1 = new Point (4, 5);p1 p2

4 5

p2 = p1;p1 p2

4 5

p2.x = 10; p1 p2

10 5

ELS - 3 février 2003

Page 45: Programmation Objet Avec Java

POO avec Java - 37 - Objets et types abstraits

class MyPoint {public int x, y;

public MyPoint(int x, int y){

this.x = x; this.y = y;}

public MyPoint deepCopy() {return new MyPoint

(this.x,this.y);}

}

class MyRectangle {public MyPoint p1, p2;

public MyRectangle (MyPoint p1,MyPoint p2) {

this.p1 = p1; this.p2 = p2;}

public MyRectangle shallowCopy() {// Copie superficielle

return new MyRectangle(this.p1, this.p2);

}

public MyRectangle deepCopy() {// Copie profonde

return new MyRectangle(this.p1.deepCopy(),this.p2.deepCopy());

}}

public class ObjectCopy {public static void main (String [] arg) {

MyPoint p1 = new MyPoint(5, 5);MyPoint p2 = new MyPoint (10, 10);

MyRectangle r0, r1, r2, r3;

r0 = new MyRectangle (p1, p2);

r1 = r0;

// Partage d'objets

r1 = r0.shallowCopy();

// Copie superficielle//(limitée à 1 niveau)

r2 = r0.deepCopy();

// Copie profonde (récursive)}

}

r0 p1 5 5

10

p2

10r1

r0 p1 5 5

10

p2

10

r2 p1

p2

r0 p1 5 5

10

p2

10

r2 p1

p2 5 5

10 10

ELS - 3 février 2003

Page 46: Programmation Objet Avec Java

POO avec Java - 38 - Objets et types abstraits

2.4.4 Passage de paramètresDans un but de simplification, Java ne propose pour les méthodes qu’un seul mécanisme de passage des paramètres:

Les paramètres de type primitif sont passés par valeur

Les paramètres de type objet sont passés par référence

Ainsi, l’exécution du programme présenté ci-après affichera:

Resultat 1-Bye Bye-Hello B2

import java.awt.*;public class Essai {

public static void uneMethode( int x,JButton b1,JButton b2) {

x = x + 1; // Modification de la valeurb1.setLabel("Bye Bye"); // Modification de la valeurb2 = new JButton ("Bye Bye") ; // Modification de la référence

}public static void main (String [] arg) {

JButton b1 = new JButton ("Hello B1");JButton b2 = new JButton ("Hello B2");int valeur = 1;uneMethode(valeur, b1, b2);System.out.println ("Resultat = " +

valeur + "-" +b1.getLabel() + "-" +b2.getLabel()

);}

}

Quelques explications..

Dans les deux cas, qu’il s’agisse d’un paramètre de type primitif ou de type objet, un mécanisme identique est mis en oeuvre: la valeur du paramètre effectif est copiée dans le paramètre formel de la méthode.

Ainsi, dans le cas d’un paramètre de type primitif, la méthode manipule une copie de la valeur du paramètre effectif.

Alors que dans le cas d’un paramètre de type objet, la méthode manipule une copie de la référence. En sortie, la méthode peut donc avoir modifié la valeur de l’objet au moyen d’un envoi de message. Par contre la référence même de l’objet ne peut être en aucun cas modifiée par la procédure.

ELS - 3 février 2003

Page 47: Programmation Objet Avec Java

POO avec Java - 39 - Objets et types abstraits

2.5 VARIABLES DE CLASSES ET VARIABLES D’INSTANCELes variables de classe sont utilisées pour mémoriser des informations globales qui concernent la classe elle-même et qui sont partagées par toutes les instances.

Une variable de classe est introduite par le mot-clé «static».

En mémoire, une variable de classe n’existe qu’en un seul exemplaire, quel que soit le nombre objets instanciés.

Les variables d’instances sont, - comme leur nom l’indique -, propres à chaque instance : un jeux de variables d’instance existera en mémoire pour chaque instance créée.

De manière identique, les méthodes d’instances sont des méthodes qui seront utilisées en envoyant le message correspondant aux objets mêmes (instances de la classe), alors que les méthodes de classe, - caractérisées par le mot-clé «static» -, seront utilisées en envoyant le message correspondant directement à la classe (que l’on peut considérer comme un objet).

class MaClasse {

private static int vdc ;// une variable de classeprivate int vdi ; // une variable d’instance

public static {// Constructeur de classe : pour initialiser les variables de classe

vdc = 20 ;}

public MaClasse() {//Constructeur classique : pour initialiser les variables d’instance

vdi = 40 ;}

public static void mdc () {// une méthode de classe

vdc++;

vdi++; // une méthode de classe ne

peut pas accéder auxvariables d’instances

}

public void mdi() {// une méthode d’instance classique

vdc++;vdi++;

}}

ELS - 3 février 2003

Page 48: Programmation Objet Avec Java

POO avec Java - 40 - Objets et types abstraits

Imaginons les quelques lignes de code suivantes:

MaClasse o1 = new MaClasse() ;MaClasse o2 = new MaClasse() ;MaClasse.mdc(); // Invocation de la méthode de classeo1.mdi() ; // Invocation de la méthode d’instance

En mémoire, nous obtenons le schéma suivant :

Comme deuxième exemple, voici une classe qui compte ses instances : à chaque créa-tion d'objet, le constructeur, qui est invoqué automatiquement, incrémente la variable d'instance «nombreDInstances».

public class MesInstancesSontComptees {// Variable de classe comptant le nombre d'instancesprivate static int nombreDInstances = 0;

MesInstancesSontComptees () {// Constructeur// Incrémente le compteur à chaque création d'instance

nombreDInstances++ ;}

public static int nbInstances () {// Méthode d'accès à la variable de classe

return nombreDInstances;}

vdc

mdc

MaClasse

vdi

mdi

o1vdi

mdi

o2

22 21 20

41 40

40

new

new

ELS - 3 février 2003

Page 49: Programmation Objet Avec Java

POO avec Java - 41 - Objets et types abstraits

}

2.5.1 Initialisation d'une variable de classeDe manière identique aux variables d'instance, la déclaration d'une variable de classe peut comporter un «initialiseur» :

static int v = 10 ;

Cette initialisation aura lieu au moment du «chargement de la classe», c'est à dire dès l'instant que la classe est utilisée.

2.5.2 Accéder aux variables de classe et aux variables d’instance

Les variables d’instance ne peuvent être accédées que par les méthodes d’instance. En effet, comme le montre la figure du premier exemple (voir plus haut), ce sont les méthodes d’instance qui peuvent agir directement sur les instances.

Par contre, une méthode de classe ne peut agir qu’au niveau de la classe : elle n’a pas accès aux variables d’instance qui existent en plusieurs exemplaires, avec un exem-plaire par instance.

☺ Notons que les variables de classe sont accessibles aussi bien par les méthodes de classe que par les méthodes d’instance. Une variable de classe est ainsi partagée par toutes les instances.

2.5.3 Le constructeur de classeL’équivalent du «constructeur» existe pour les variables de classe.

Un tel constructeur est désigné par le mot-clé «static». Ce constructeur sera invoqué au moment du chargement de la classe. Comme ci-après :

static {:v = 20 ; // Initialisation d'une variable de classe:

}

A l'instar des méthodes de classe, le constructeur de classe ne peut accéder qu'aux variables de classe. L'accès aux variables d'instance (informations propres aux objets) lui est interdit.

ELS - 3 février 2003

Page 50: Programmation Objet Avec Java

POO avec Java - 42 - Objets et types abstraits

2.6 POURQUOI «ABSTRAIT» ?Dans le concept de «type abstrait», il n’y a pas lieu d’être effrayé par le mot «abstrait».

Ce mot, qui s’apparente généralement à des éléments difficiles à comprendre, doit être compris, au contraire, dans le sens de la simplification. Un type de données «abstrait» est décrit uniquement par ses éléments essentiels: les opérations qui permettent d’agir avec des objets de ce type.

Ainsi, le programmeur qui utilise un «type abstrait» n’a pas à se soucier de détails superflus: il lui importe seulement de savoir utiliser ce type. Le «concret», à savoir l’implantation du type, la manière dont il est réalisé (variables, instructions,..) ne l’inté-resse pas et ne ferait qu’encombrer son esprit.

En réalité, tout programmeur a déjà utilisé des «types abstraits» sans le savoir. En effet, lorsque dans un algorithme on doit manipuler des nombres entiers, on ne se préoccupe pas de la manière dont sont représentés ces nombres en mémoire, mais uniquement des propriétés des 4 opérations habituelles +, -, / et *.

2.7 INTÉRÊT DES TYPES ABSTRAITSLa notion de type abstrait présente au moins deux avantages.

Le premier a trait à la simplicité de la programmation: pour utiliser un type de données, un programmeur n’a plus besoin de se soucier de l’ensemble des valeurs, mais uniquement de l’ensemble des opérations;Le deuxième a trait à l’indépendance des modules qui composent une applica-tion informatique.Notamment, le bout de programme qui utilise un type abstrait sera complète-ment indépendant du bout de programme qui implémente le type abstrait (qui le réalise).

En distinguant très clairement «spécification» et «implémentation», on peut constater que les modifications apportées à un module se répercutent de la manière suivante au niveau d’un programme client (utilisateur du module):

changement de l’implémentation client non modifié ☺

changement de la spécification (rajouts) client non modifié ☺changement de la spécification (autres) client modifié

On s’intéresse d’abord aux opérations..Considérons un programme qui doit traiter deux types de données: des dates et des poids. Une donnée de chacun de ces types peut s’exprimer par un nombre entier:

Le nombre de jours écoulés entre le 1er janvier de l’an 1000 et cette date;

ELS - 3 février 2003

Page 51: Programmation Objet Avec Java

POO avec Java - 43 - Objets et types abstraits

Le nombre de grammes de ce poids.

Ces deux types de données utilisent bien le même domaine de valeurs: l’ensemble des nombres entiers. Cependant, ils diffèrent sur les opérations applicables à ces valeurs.

En effet, on peut très bien imaginer d’avoir à soustraire ou à additionner deux poids. On peut aussi soustraire deux dates pour trouver le nombre de jours qui les séparent. Par contre, on voit bien qu’il n’y a aucun intérêt à additionner deux dates.

On peut imaginer une opération «jourDeLaSemaine», qui, appliquée à une date, retournerait «lundi», ou «mardi», etc.. Une telle opération n’aurait pas de sens avec les poids.

En réalité, l’ensemble des valeurs prises par le type de données intéresse peu le pro-grammeur-client qui préfère se concentrer sur ce que l’on peut faire avec des objets plutôt qu’à la manière dont ces objets sont représentés en mémoire.

Indépendance par rapport à l’implantation..Considérons à nouveau le type «Date».

Le programmeur-client s’intéressera uniquement aux opérations applicables aux objets de type «Date». Peu lui importe finalement de savoir si ces dates sont représentées en mémoire sous la forme de nombres entiers, de triplets (jour, mois, année), de textes ou autres.

Ce n’est qu’au moment de l’implantation de ce type, c’est-à-dire de sa réalisation, que le programmeur-réalisateur pourra choisir entre telle ou telle représentation. Ce choix tiendra compte d’un certain nombre de critères comme la performance, la robustesse, l’élégance de la solution,..

Par la suite, le programmeur-réalisateur pourra en tout temps changer de représenta-tion, sans même avoir à prévenir le programmeur-client.

Cette indépendance entre utilisation et implantation sera assurée si le type de données est utilisé sans tenir compte de sa représentation, mais uniquement par le biais des opé-rations qui lui sont applicables: raison pour laquelle la structure d’informations est encapsulée [Voir paragraphe - Encapsulation de la structure d’informations - page 17].

Un exemple.. ré-implantation de la classe «Rectangle»La classe «Rectangle» que nous avons présentée en détails dans le [“Programme 2:”, page 13], est une implantation possible du type abstrait «Rectangle_I».

D’autres implantations pourraient être imaginées.

ELS - 3 février 2003

Page 52: Programmation Objet Avec Java

POO avec Java - 44 - Objets et types abstraits

Notamment, plutôt que de représenter la structure de données au moyen d’un point, d’une largeur et d’une hauteur, on peut préférer représenter un rectangle par deux points, le point supérieur gauche et le point inférieur droit.

Le programme [“Programme 6:”, page 44] présenté ci-après décrit la nouvelle version de la classe «Rectangle».

Insistons sur le fait que l’utilisation de cette classe est rigoureusement identique à la précédente. L’interface du programme-client, présenté au préalable, est donc toujours applicable (voir [“Programme 1:”, page 11]).

Programme 6: une nouvelle implantation de «Rectangle»

class Rectangle implements Rectangle_I {// Variables d’instance

private int x1; // Point supérieur gauche

private int y1;private int x2; // Point inférieur droit

private int y2;

// Constructeur (s)

public Rectangle () {x1 = y1 = 0;x2 = y2 = 0;

}

public Rectangle (int pX, int pY, int largeur, int hauteur){

x1 = pX;y1 = pY;x2 = x1 + largeur;y2 = y1 + hauteur;

}

public Rectangle (Rectangle r) {.. à adapter..

}

x, y largeur

hauteur

x1, y1

x2, y2

ELS - 3 février 2003

Page 53: Programmation Objet Avec Java

POO avec Java - 45 - Objets et types abstraits

// Modificateurs

public void agrandir(int delta) {.. à adapter..}public void déplacer (int deltaX, int deltaY){..à adapter..}public void setX(int x) {.. à adapter..}public void setY(int y) {.. à adapter.. }public void setLargeur(int lg) {.. à adapter..}public void setHauteur(int ht) {.. à adapter..}

// Observateurs

public int getX () {.. à adapter..}public int getY (){.. à adapter..}public int getLargeur(){.. à adapter..}public int getHauteur (){.. à adapter..}public boolean contientPoint (int pX, int pY){.. à adapter..}

}

2.8 LA PROGRAMMATION OBJET ET LES TYPES ABSTRAITS Jusqu’à présent, il a pu sembler au lecteur que la discussion portait principalement sur la notion de type abstrait au dépend de la programmation objet elle-même.

En fait, les deux concepts se rejoignent très intimement dans le sens où la programma-tion objet a complètement intégré et a basé sa philosophie sur la notion même de type abstrait.

Mais, pour réaliser une application objet, il ne suffit pas que d’écrire des types abstraits. Il faut également intégrer et assimiler un autre concept lié à la manière d’appréhender le comportement dynamique des programmes.

Programmation objet versus programmation procédurale

Définition 11: Le concept de la programmation procédurale

Dans une programmation procédure classique, une application est vue par le program-meur comme un ensemble de fonctions qui appellent d’autres fonctions.

La vision est fortement centralisée. Dans le cas idéal, on peut représenter la structure du programme au moyen d’une arborescence de fonctions qui peuvent faire appel aux services offerts par des types abstraits.

ELS - 3 février 2003

Page 54: Programmation Objet Avec Java

POO avec Java - 46 - Objets et types abstraits

Figure 2: Le modèle procédural

Définition 12: Le concept de la programmation objet

En programmation objet, une application est vue comme un ensemble d’objets qui communiquent et collaborent en s’envoyant des messages.

La vision est maintenant fortement décentralisée: chaque objet dispose en général d’un comportement autonome.

La structure d’un programme objet sera représentée au moyen d’un réseau d’objets associés les uns aux autres.

Figure 3: Le modèle Objet - Diagramme de collaboration

Programmeprincipal

Sous-programme 1

Sous-programme 2

Sous-programme X

Sous-programme Y

Sous-programme Z

Sous-programme T

Type abstrait

Type abstrait

Appel de procédure

Objet5

Objet1

Objet2

Objet4

Objet3

Objet6

Objet7

message 1

message 1.1

message 1.2message 2message 3

message 2.1 message 2.1.1

ELS - 3 février 2003

Page 55: Programmation Objet Avec Java

POO avec Java - 47 - Objets et types abstraits

Le diagramme de colaboration (notation UML) montre l’enchaînement de l’envoi des messages et leur imbrication. Par exemple, l’envoi des messages 1.1 et 1.2 résulte de l’exécution de la méthode invoquée par la réception du message 1 par l’objet 2.

Figure 4: Le modèle Objet - Diagramme de séquence

Le diagramme de séquence (notation UML) montre également l’enchaînement de l’envoi des messages et leur imbrication. Le temps est représenté verticalement. Les rectangles fins verticaux représentent l’exécution des méthodes, résultant de l’envoi d’un message

Un objet est-il un type abstrait ?Dans une première approche, un objet peut être considéré comme un type abstrait dans le sens où sa structure interne est généralement encapsulée [Voir paragraphe - Encapsulation de la structure d’informations - page 17] et que l’on peut agir sur cet objet uni-quement par le biais d’opérations faisant partie de son interface.

Toutefois, la notion même d’objet va plus loin que la notion classique de type abstrait.

Définition 13: Un objet est plus qu’un type abstrait

La notion classique de type abstrait est associée directement à la notion de structure de données.

Des objets passifs.. Dans la plupart des cas, les objets peuvent être assimilés à des structures de données, c’est-à-dire à des structures plutôt passives. De tels objets sont appe-lés objets passifs. Si on leur demande d’accomplir certains traitements, ils obéissent, mais ne font preuve en général d’aucune initiative par eux-mêmes.

Des objets actifs.. Dans le cadre d’une application objet, un objet peut jouer des rôles beaucoup plus actifs: on rencontrera notamment des objets acteurs, initiateurs de messa-ges, caractérisés par un flot de contrôle qui leur est propre (un ou plusieurs

Objet 1 : O1 Objet 2 : O2 Objet 3 : O3 Objet 4 : O4 Objet 5 : O5 Ob jet 6 : O6 Objet 7 : O7

message1( ) message1.1( )

message1.2( )

message2( )message2.1( )

message2.1.1( )

message3( )

ELS - 3 février 2003

Page 56: Programmation Objet Avec Java

POO avec Java - 48 - Objets et types abstraits

«threads»), et qui s’exécutent en parallèle au reste du programme. Les contrô-leurs du programme, les contrôleurs de périphériques (sockets, capteurs,..) sont des exemples d’objets acteurs.

Classes et types abstraits En programmation objet, le programme est structuré en classes qui définissent un com-portement et une structure interne qui sera commune à tous les objets qui seront instan-ciés à partir de cette classe.

☺ La classe n’oblige pas à encapsuler les informations..

La classe offre toute une panoplie d’outils qui permettront de définir des types abstraits: notamment, le programmeur aura la possibilité d’encapsuler la structure des informa-tions au moyen des modes de protection (private, protected et public).

☺ La classe connaît l’héritage

Par rapport à la théorie classique des types abstraits, la programmation objet a apporté la notion d’héritage: une classe peut hériter de la structure et du comportement d’une classe déjà définie. [Voir paragraphe - Java et le mécanisme d’héritage - page 54]

☺ Une classe peut définir des objets actifs..

Une classe peut définir une ou plusieurs activités concurrentes qui seront mises à profit pour réaliser des objets actifs tels que des contrôleurs de périphériques. La théorie clas-sique des types abstraits est dédiée principalement à la mise en oeuvre de structures de données passives.

2.9 LA STRUCTURE D’UN PROGRAMMEComme une application Java peut contenir un nombre relativement énorme de classes, l’outil naturel qui s’offre au programmeur et d’organiser ces classes au sein d’une ou de plusieurs hiérarchies de paquetages : nous trouverons dans un même paquetage toutes les classes qui collaborent à une même fonctionnalité.

2.9.1 Les paquetagesUn paquetage Java («package») est un ensemble de classes et d’interfaces plus ou moins affiliées les unes aux autres, et souvent destinées à coopérer dans le cadre d’une même application;

A titre d’exemple, le paquetage «java.awt» rassemble tous les éléments de Java pou-vant être affichés dans le cadre d’une interface graphique.

☺ Un paquetage définit un espace de noms

ELS - 3 février 2003

Page 57: Programmation Objet Avec Java

POO avec Java - 49 - Objets et types abstraits

Les noms donnés aux classes et interfaces regroupés dans un même paquetage doivent être différents les uns des autres. Deux classes différentes ou deux interfaces différentes pourront porter de même nom, à condition d’être distribuées dans des paquetages diffé-rents.

Utilisation d’un paquetageL’utilisation d’un paquetage est très simple: il suffit de préfixer un identificateur par son nom de paquetage; ainsi, utiliser «ClasseA» du paquetage «MonPackage» peut se faire ainsi:

class Test {void uneMethode() {MonPackage.ClasseA mpA = new MonPackage.ClasseA();int i = mpA.uneMethodeDeA();

}

ImportationUn inconvénient évident est qu’il faut retaper le nom du paquetage à chaque fois;

☺ Utiliser «import» pour ouvrir le contexte d’un paquetage.

import MonPackage.ClasseA;

class Test {void uneMethode() {ClasseA mpA = new ClasseA();

Il est également possible d’ouvrir le contexte du paquetage entier, et de définir ainsi toutes les classes du paquetage comme importées; la syntaxe est alors la suivante:

import MonPackage.*;

import java.awt.*;

Toutes les classes sont importées, mais pas celles des sous-paquetages ..

Les hiérarchies de paquetagesUn paquetage peut contenir d'autres paquetages …

L’importation d’une class Xxx située dans le sous-paquetage Bbb du paquetage Aaas’écrira ainsi, en explicitant le chemin d’accès:

ELS - 3 février 2003

Page 58: Programmation Objet Avec Java

POO avec Java - 50 - Objets et types abstraits

import Aaa.Bbb.Xxx ;

Relations avec le système de gestion de fichiersChaque paquetage est placé dans son propre répertoire, dont le nom correspond à ce-lui du paquetage.

Ainsi, une hiérarchie de paquetages devra être mise en place en élaborant une hiérar-chie correspondante de répertoires dans le système de fichiers.

2.9.2 Organisation des fichiers sources

Composition d’un paquetageLes classes qui composeront le paquetage seront écrites dans des fichiers portant l’ex-tension «.java». Ces fichiers seront placés dans le même répertoire, celui du paqueta-ge.

Composition d’un fichierEn général, chaque fichier contiendra une classe qui sera déclarée avec le mot-clé «pu-blic». Cette dernière doit peut être utilisée en dehors du paquetage. Les autres classes, - non publiques -, ne seront visibles qu’à l’intérieur du paquetage.

Un fichier peut contenir un nombre indéterminé de classes !

Un seul composant «public» par fichier !

Java impose que chaque fichier ne contienne qu’un seul élément exportable, désigné par le mot-clé «public». Ainsi, les « composants secondaires », nécessaires à la mise en oeuvre du composant principal, ne seront jamais visibles en dehors du paquetage.

Le fichier doit annoncer le paquetage auquel il appartient

package A.B; // 1ère instruction// Si omission, il s’agit du paquetage «anonyme»,// qui correspond au répertoire de travail

import X.Y.*; // Importations valables uniquement pour le fichier

2.9.3 Amorce de l’application: la classe principale (main)Parmi les classes publiques, une au moins1 comportera une fonction «main» qui sera invoquée au moment du lancement du programme :

1. Plusieurs classes peuvent comporter une fonction main (ce qui permet de les tester indé-pendamment les unes des autres).

ELS - 3 février 2003

Page 59: Programmation Objet Avec Java

POO avec Java - 51 - Objets et types abstraits

> java Xxx

Ceci provoque l’exécution de la machine virtuelle Java, qui invoque la fonction «main» de la classe «Xxx» (qui doit être une classe publique).

ELS - 3 février 2003

Page 60: Programmation Objet Avec Java

POO avec Java - 52 - Objets et types abstraits

ELS - 3 février 2003

Page 61: Programmation Objet Avec Java

POO avec Java - 53 - Héritage et polymorphisme

3 Héritage et polymorphisme

En programmation orientée objet, la structure du programme s'appuie sur deux concepts fondamentaux: la modularité et la hiérarchie.

La modularité est caractérisée par le fait que chacun des composants du système sera placé dans une unité fonctionnelle indépendante. Une structure basée sur la modularité se prêtera bien à la réutilisation de code. En effet, si chaque module est conçu de manière abstraite, pour résoudre des problèmes généraux, il est fort probable que l'on puisse les réutiliser dans un autre contexte. Par exemple, un architecte peut réutiliser la spécification d'un « mur » d'un bâtiment à l'autre. Toutefois, en réutilisant une telle spé-cification, il est probable qu'il faille redéfinir certains aspects. Notamment, le mur d'un bâtiment commercial pourra différer du mur d'une maison particulière par son système électrique. Ainsi, notre architecte sera amené à structurer ses composants structurels d'une manière hiérarchique, en regroupant ses spécifications par niveaux, allant du général au particulier. La structure qu'il mettra en place aura l'allure d'un arbre généalogique.

ELS - 3 février 2003

Page 62: Programmation Objet Avec Java

POO avec Java - 54 - Héritage et polymorphisme

La possibilité de structurer les composants de manière hiérarchique est connue en informatique sous le terme d'héritage. Ce concept, qui a déjà plus de 20 ans, est extrê-mement efficace en termes de structuration, mais aussi de réutilisation du logiciel.

3.1 JAVA ET LE MÉCANISME D’HÉRITAGE Une classe utilisera l’héritage pour reprendre à son compte toutes les propriétés qui ont été définies dans une autre classe. C’est ce que l’on appelle l’héritage simple, mécanis-me proposé en Java. D’autres langages comme C++ proposent l’héritage multiple, qui permet de reprendre les propriétés de plusieurs classes à la fois.

Figure 5: Héritage simple et héritage multiple

3.1.1 Héritage simple contre héritage multipleDans la version «héritage simple», la classe Homme devra redéfinir l’implémentation de la méthode mangerBanane() (l’homme, en effet, a des manières que le singe ne connaît pas).

L’héritage multiple, si puissant qu’il soit, a le grand défaut de présenter certaines ambi-guïtés: un étudiant mange une banane à la manière d’un singe ou à la manière d’un homme ? le programmeur devra lever l’ambiguïté en spécifiant l’origine de la méthode mangerBanane().

Dans la plupart des cas, l’ambiguïté est simple à lever. Toutefois il peut arriver que le graphe d’héritage devienne fort complexe à force d’héritage multiple et difficile à appréhender. Dans le cas de figure précédent, il pourrait arriver que la classe Hommehérite d’autres classes dont certaines héritent elles-même de la classe Singe.

Quoiqu’il en soit, à tort ou à raison, les concepteurs de Java ont décidé de se cantonner à l’héritage simple. A titre de comparaison, un langage à succès comme Python a choisi

Version JavaHéritage simple

Homme

+ mangerBanane()+ grimperAuxArbres()

Etudiant

+ mangerBanane()

Singe

Version C++Héritage multiple

Homme+ mangerBanane()+ grimperAuxArbres()

Etudiant

+ mangerBanane()

Singe

ELS - 3 février 2003

Page 63: Programmation Objet Avec Java

POO avec Java - 55 - Héritage et polymorphisme

d’implémenter l’héritage multiple. Alors que C#, le nouveau-né de microsoft, ne pro-pose que l’héritage simple, tout comme Smalltalk, précurseur de Java.

Pourtant, grâce à l’héritage multiple, il est possible de déclarer des variables qui seront caractérisées par un typage multiple. Ainsi, un étudiant pourra dans certaines parties du programme être considéré comme un objet de type Singe, lorsqu’il s’agira par exem-ple de le faire grimper aux arbres. Dans d’autres parties du programme, on le manipu-lera comme un objet de type Homme, quand on lui demandera par exemple de payer sa cotisation.

Java résout ce problème par le biais du concept d’interface: une classe Y peut hériter d’une seule classe, la classe X, mais peut par contre implémenter plusieurs interfaces, comme par exemple les interfaces A et B. Un objet de la classe X possède ainsi plu-sieurs types: X, Y, A et B.

Figure 6: Multi-typage et implémentation d’interfaces en Java

.

Dans l’exemple ci-dessus, la classe Etudiant hérite de la classe Homme et implémente les deux interfaces Singe et MangeurDeMacDo. Un étudiant obéit ainsi aux définitions d’au moins 4 types différents: Etudiant, Homme, Singe et MangeurDeMacDo.C’est ce que l’on appelle le typage multiple, à défaut d’héritage multiple.

Homme

mangerBanane();grimperAuxArbres();

Etudiant

+ mangerBanane()

<<interface>>Singe

mangerUnMacDo()

<<interface>>MangeurDeMacDo

+ grimperAuxArbres();+ mangerUnMacDo()

ELS - 3 février 2003

Page 64: Programmation Objet Avec Java

POO avec Java - 56 - Héritage et polymorphisme

Figure 7: Héritage multiple en C++

.

L’héritage multiple implique tout naturellement le typage multiple. La figure ci-dessus définit un étudiant en C++; cet étudiant obéit également aux 4 types Etudiant, Homme, Singe et MangeurDeMacDo. A la différence de la solution Java, la classe Etudiant hérite bel et bien des méthodes grimperAuxArbres et mangerUnMacDo, et n’a donc pas besoin de les implémenter. Par contre, il faut encore lever l’ambiguïté de la méthode mangerBanane.

3.1.2 Héritage simple en JavaPour présenter brièvement ce mécanisme, supposons que le programmeur ait défini un lien d'héritage entre deux classes «A» et «B» en écrivant que la classe «B» hérite de la classe «A».

class A {/* code de la classe A */

};

class B extends A {// La classe B « hérite » des propriétés de la// classe A et rajoute son propre code

/* code rajouté par la classe B */};

Homme+ mangerBanane();+ grimperAuxArbres();

Etudiant

+ mangerBanane()

Singe

+ mangerUnMacDo()

MangeurDeMacDo

Homme:mangerBanane()

ELS - 3 février 2003

Page 65: Programmation Objet Avec Java

POO avec Java - 57 - Héritage et polymorphisme

Figure 8: La notation UML pour montrer que la classe «B» hérite de la classe «A»

Définition 14: Héritage

Si «B» hérite de «A»: tous les membres1 qui ont été définis dans la classe «A» devien-nent des membres à part entière de la classe «B», et toute modification ultérieure de «A» sera automatiquement reportée sur «B»: il s'agit en fait d'un « super COPIER-COLLER », un copier-coller dont la mise à jour est automatique.

On dira que la classe «B» est une classe dérivée de «A» ou encore une sousclasse de «A», et que «A» est la superclasse de «B»

De manière plus précise, on peut dire que la classe «B», par héritage, partage toutes les propriétés de la classe «A», à savoir: sa structure et son comportement.

Héritage de la structureLa sousclasse hérite des variables d’instance et des variables de classe de sa superclasse. Elle a la possibilité de rajouter des variables qui seront propres à son usage personnel. Par contre, elle ne pourra pas en supprimer: l'héritage doit être accepté dans son ensemble !

Héritage du comportementLa sousclasse hérite des méthodes d’instance et des méthodes de classe qui ont été définies au niveau de sa superclasse. Ici encore, la sousclasse a la possibilité de rajouter de nouvelles méthodes. Elle pourra par ailleurs redéfinir les méthodes dont elle hérite dans le but d’adapter leur comportement aux spécificités de la nouvelle classe.[Voir paragraphe - La redéfinition des méthodes - page 80]

B

A est la superclasse de BA

B est la sousclasse de A

1. Les « membres » regroupent les variables et les méthodes

ELS - 3 février 2003

Page 66: Programmation Objet Avec Java

POO avec Java - 58 - Héritage et polymorphisme

Un exemple théorique

Une classe «X»: la superclasse

class X extends1 Object {private int vi1; // Deux variables d'instance

private int vi2;public void M1 () {..code..}// Trois méthodes

public void M2 () {..code..}public void M3 () {..code..}

}

Deux sousclasses «Y1» et «Y2»:

la classe Y1 rajoute une méthode M4la classe Y2 rajoute une variable d’instance vi3, elle rajoute également la méthode M5 et redéfinit la méthode M2 en lui donnant une nouvelle implémen-tation

Figure 9: Résultat de l'héritage de X (diagramme UML)

La figure suivante montre les membres caractérisant chacune des classes Y1 et Y2.

1. La classe «X» dérive de la classe «Object». C'est en fait implicite. Ecrire « class X {..} » aurait suffit: toutes les classes de java héritent de la classe « Object ».

class Y1 extends X {public void M4 () {

..code..}

}

class Y2 extends X {private int vi3 ;public void M2 () {..code..}public void M5 () {..code..}

}

la superclasse

Méthodes

- vi1- vi2

+ M1

+ M3+ M2

relationd'héritage

Variables d'instance

Méthodes+ M2

- vi3

+ M5

X

Une méthode

Y1

Y2

+ M4

les sousclasses de X

Une variable d'instance

relationd'héritage

ELS - 3 février 2003

Page 67: Programmation Objet Avec Java

POO avec Java - 59 - Héritage et polymorphisme

Bien que les classes Y1 et Y2 héritent des variables vi1 et vi2, le code de ces deux classes n’y a pas accès! Elles sont en effet déclarées avec un mode de protection privé dans la classe X.

3.1.3 Héritage: quel intérêt?On peut citer au moins 3 avantages à retirer de l’exploitation du concept d’héritage: une meilleure structuration du programme, une réutilisation potentielle du code, et enfin une économie de la mémoire.

Une meilleure structuration du programmeObtenue par une mise en évidence des parties générales (superclasses) et des parties spé-cialisées (sousclasses). Cette amélioration est bénéfique du point de vue de la mise au point des programme et de leur modification future (maintenance), et donc la qualité du logiciel en général.

La réutilisation de codePlacé sous l'angle de la réutilisation de code, l'héritage permet d'économiser beaucoup d'écriture: les sousclasses réutilisent le code qui a été écrit - une fois seulement - au ni-veau des méthodes de la classe de base. Par la suite, toute modification apportée aux méthodes de la classe de base sera automatiquement propagée au niveau des sousclas-ses!

L'héritage et l'utilisation de la mémoireL'héritage permet de gagner de la place en mémoire au niveau des méthodes: les métho-des héritées ne seront pas dupliquées, leur code sera placé au niveau de la classe de base.Par contre, on ne gagne rien aux niveau des variables d'instances: ces dernières sont du-pliquées. Un objet occupe en mémoire toute la place qui est nécessaire pour stocker ses propres variables d'instance, mais aussi toutes les variables d'instance dont il hérite.

Classe Y1

Classe Y2

Variablesd'instance

Méthodes

vi1 (hérité)vi2 (hérité)

vi1 (hérité)vi2 (hérité)vi3

M1 (hérité)M2 (hérité)M3 (hérité)M4

M1 (hérité)M2 (redéfini)M3 (hérité)M5

ELS - 3 février 2003

Page 68: Programmation Objet Avec Java

POO avec Java - 60 - Héritage et polymorphisme

Considérons l'exemple suivant:

class Animal {private String nom; // Variable: nom de l'animal

:}class Oiseau extends Animal {

private boolean peutVoler;// Variable indiquant si l'oiseau est capable// ou non de voler:

}class Perroquet extends Oiseau {

private String vocabulaire;// Variable contenant le vocable de base d'un perroquet:

}

Un objet créé à partir de la classe Perroquet occupera en mémoire toute la place nécessaire au stockage des 3 informations:

1. le nom (hérité),2. le booléen indiquant qu'il peut voler (hérité également),3. son vocabulaire (attribut spécifique).

Rappelons encore une fois que le code de la classe Perroquet n’a pas accès aux variables nom et peutVoler bien qu’elle en hérite: ces dernières étant déclarées avec un mode d’accès privé dans les classes Animal et Oiseau

Figure 10: Zone mémoire occupée par un perroquet

3.1.4 Edition de liens dynamiqueComme nous l’avons mentionné plus haut, l'héritage donne la possibilité de redéfinir tel-

un "animal"en mémoire

nom

un "oiseau"en mémoire

nom

un "perroquet"en mémoire

peutVoler

nom

peutVoler

vocabulaire

hérité hérité

hérité

- nom :

Animal

- peutVoler :

Oiseau

- vocabulaire:

Perroquet

ELS - 3 février 2003

Page 69: Programmation Objet Avec Java

POO avec Java - 61 - Héritage et polymorphisme

le ou telle méthode.

Une question: lorsqu'un objet reçoit un message, et que la méthode correspondante existe en plusieurs exemplaires au sein de la hiérarchie, quelle sera la méthode exécu-tée ?

Le principe consiste à invoquer la méthode la plus appropriée, celle qui est la plus pro-che de l’objet auquel on envoie le message.

Définition 15: Edition de liens dynamique

Si deux méthodes dans la hiérarchie ont le même nom, Java exécute la première méthode trouvée en partant du type courant de l'objet, et en remontant dans les classes parentes.

Comme le lien vers la méthode à invoquer est déterminé dynamiquement au moment de l’exécution, on parle d’édition de liens dynamique.

Le type courant d'un objet ?Commençons par revoir les notions de type déclaré et de type courant. Ces deux con-cepts nous permettrons de comprendre le phénomène de liaison dynamique.

Objets: type de déclaration et type courant A titre d’illustration, nous allons travailler avec la hiérarchie «Animal», simplifiée à l’extrême:

ELS - 3 février 2003

Page 70: Programmation Objet Avec Java

POO avec Java - 62 - Héritage et polymorphisme

class Animal {

private String nom = ""; // Nom de l'animal

public Animal (String nomAnimal) {nom = nomAnimal;

}

public void direQuelqueChose () {System.out.println

("je ne sais par parler..");}

}

class Oiseau extends Animal {

private boolean peutVoler = false;

public void apprendreAVoler () {peutVoler = true;

}

public void direQuelqueChose () {// Redéfinition

System.out.println ("Cui cui");}

public void blesser() {peutVoler = false;

}}

ELS - 3 février 2003

Page 71: Programmation Objet Avec Java

POO avec Java - 63 - Héritage et polymorphisme

Définition 16: Type courant et type déclaré

A l'exécution du programme, le type courant d'un objet peut correspondre à l'une ou l'autre des classes appartenant à la hiérarchie associée au type déclaré.

Considérons à présent l'exemple de la hiérarchie Animal et supposons que l'on utilise ainsi cette hiérarchie:

Animal a1 = new Animal ("jojo");Animal a2 = new Oiseau ("coco");Oiseau o = new Oiseau ("zozio");

type de déclaration type courant

a1 Animal Animal

a2 Animal Oiseau

o Oiseau Oiseau

Animal unAnimal = new Oiseau ("Bebete");

Oiseau unOiseau = new Oiseau ("Jaccot");

Type courant = type de l'objetLe type courant est dynamique, connuuniquement à l'exécution

Type déclaré = type de la variableLe type déclaré définit l'ensemble des opérations qui peuvent être accomplies sur la variable.Le compilateur vérifie que cette règle est respectée

ELS - 3 février 2003

Page 72: Programmation Objet Avec Java

POO avec Java - 64 - Héritage et polymorphisme

Voici quelques envoi de messages...a1.apprendreAVoler();

// REFUSE par le compilateur !!

a2.apprendreAVoler();// REFUSE par le compilateur !!

o.apprendreAVoler(); // OK

a1.direQuelqueChose(); // OK, affiche "je ne sais pas

// parler.."

a2.direQuelqueChose(); // Ok, affiche "Cui cui";

o.direQuelqueChose(); // Ok, affiche "Cui cui";

La méthode apprendreAVoler n'est pas une méthode redéfinie. Les deux premiers appels sont incorrects car le type déclaré des variables «a1» et «a2», le type Animal, ne connaît pas la méthode apprendreAVoler...

La méthode direQuelqueChose est une méthode redéfinie. Son appel est correct dans les 3 cas, car les types Animal et Oiseau, types déclarés de «a1», «a2», respec-tivement «o», connaissent tous deux cette méthode.

Le choix de la méthode à exécuter dépend du type courant de ces objets, et ne sera déterminé qu’à l’exécution du programme. Pour «a2» et «o», c'est la méthode de la classe Oiseau qui sera sélectionnée ( "Cui cui" ) alors que pour «a1», c'est celle de la classe Animal qui sera choisie ( "je ne sais par parler..").

La recherche de la méthode à exécuter a lieu dynamiquement, pendant l'exécution même du programme, raison pour laquelle ce phénomène est désigné par le terme « liaison dynamique ».

3.2 UN BILAN SUR LES MODES DE PROTECTIOND'un point de vue externe, un objet est caractérisé par un ensemble de messages aux-quels cet objet est susceptible de répondre.

Pénétrons dans l'objet: on y trouvera l’implémentation, c'est-à-dire la « mise en œuvre » de cette « vision externe ». Cette implantation sera composée d'un ensemble de méthodes et de variables d'instance.

Pour une vision externe donnée, plusieurs implémentations sont possibles. Plus ou moins efficaces, plus ou moins élégantes, peu importe... Ainsi, il est certain que deux programmeurs à qui l'on confierait la réalisation de la même classe proposeront deux implantations fort différentes

L'expérience montre aussi que pour un programmeur donné, une implémentation est sans arrêt remise sur le chantier: l'introduction de nouvelles fonctionnalités entraîne une nouvelle structuration des variables d'instance, des «bug » sont découverts, l'effica-cité de l'objet doit être améliorée, etc...

Une implémentation est donc très volatile.

ELS - 3 février 2003

Page 73: Programmation Objet Avec Java

POO avec Java - 65 - Héritage et polymorphisme

Pour le programmeur responsable de l’implémentation de l'objet, conscient du fait qu'il sera amené à la modifier fréquemment, il est très important d'avoir les «coudées fran-ches», c'est-à-dire de pouvoir modifier sa mise en œuvre sans avoir à se soucier de «l'extérieur» (c'est-à-dire sans avoir à ajuster le reste du programme qui utilise son objet).

En d'autres termes, il est important d'assurer une très grande indépendance entre son implémentation et le reste du programme.

3.2.1 EncapsulationUne manière de satisfaire à cette condition d'indépendance est de protéger les variables d'instance et certaines méthodes en les rendant inaccessibles de « l'extérieur ». Ainsi, le programmeur pourra - à sa guise - les supprimer, les modifier, ou encore en rajouter, sans que le reste du programme ne soit concerné.

Définition 17: Encapsulation

En empêchant d'accéder de l’extérieur à l'implantation d'une classe, nous satisfaisons au principe dit de l'encapsulation.

Pour réaliser cette encapsulation, Java et d’autres langages objet comme Smalltalk, C++,... mettent à disposition deux modes de protection fondamentaux: les modes privéet public.

un membre privé d'une classe «A» est utilisable uniquement par les méthodes de la classes «A»,un membre public d'une classe «A» est accessible par n'importe quelle méthode déclarée à l'intérieur ou à l'extérieur de la classe «A».

Dans ce qui suit, nous supposerons deux classes «A» et «B», «B» étant définie par déri-vation de la classe «A». Nous allons reprendre les modes publics et privés pour les ana-lyser du point de vue de l'héritage.

3.2.2 Le mode privé: «private»Ce mode de protection est introduit par le mot-clé «private».

Les méthodes publiques ne sont pas héritées: le message correspondant ne pourra pas être envoyé à un objet de la classe «B»1.

B

A

B

A

ELS - 3 février 2003

Page 74: Programmation Objet Avec Java

POO avec Java - 66 - Héritage et polymorphisme

Une méthode privée ne peut pas être redéfinie dans une sous-classe. La classe «B» peut proposer une méthode portant le même nom et les mêmes paramètres, mais cette méthode n’aura rien à voir avec la méthode privée de la classe «A»: il ne s’agit pas d’une redéfinition au vrai sens du terme (au sens de l’édition de liens dynamique).

Une variable d’instance privée déclarée dans «A» sera héritée dans le sens où elle occupera une zone mémoire chez toutes les instances de la classe «B». Toutefois, sa valeur ne sera pas accessible aux méthodes de la classe «B».

3.2.3 Le mode public: «public»Ce mode de protection est introduit par le mot-clé «public».

Les méthodes publiques sont héritées. De telles méthodes peuvent être redéfinies dans «B» pour adapter leur comportement. Dans ce dernier cas, si une méthode de la classe «B» désire invoquer la méthode de la superclasse en lieu et place de la nouvelle version, elle pourra le faire en utilisant le mot-clé « super ».

nomMethode (..) ; // invocation de la nouvelle version

super.nomMethode (..) ;// invocation de la méthode originale

Les variables d’instance publiques sont héritées. Elles sont accessibles directement par les méthodes de la classe «B», à moins que la classe «B» déclare une variable d’ins-tance de même nom. Dans ce dernier cas, on dit qu'il y a masquage de l'attribut: la variable d’instance de la superclasse continue d'exister, mais accessible uniquement par le biais du mot-clé «super».

x = 10; // affectation de la variable «x» de «B»

super.x = 10; // affectation de la variable «x» de la classe «A»

[Voir paragraphe - Masquage d’une variable d’instance - page 92]

3.2.4 Le mode protégé: «protected»Voici un troisième mode. Les choses se compliquent ! !

Mais c'est pour le bien du programmeur...

Supposons toujours nos deux classes «A» et «B», avec «B» dérivant de «A».

Dans certains cas, il serait avantageux qu'un membre « privé » de «A», interdit au public, soit tout de même accessible aux sousclasses, et notamment aux méthodes de «B».

En quelque sorte, on aimerait un mode de protection intermédiaire entre les modes « publics » et « privé »..: un mode protégé qui autoriserait l'accès aux classes de la même famille.

1. A moins, toutefois, que la méthode ne soit redéclarée dans «B»

ELS - 3 février 2003

Page 75: Programmation Objet Avec Java

POO avec Java - 67 - Héritage et polymorphisme

Introduit en C++, ce mode de protection a été repris en Java et s'appelle le mode pro-tégé1.Il est introduit par le mot-clé «protected».

Définition 18: Mode «protected»

Les variables et les méthodes protégées sont héritées, et sont accessibles:

1. par les méthodes de toutes les sousclasses, pour autant qu’il s’agisse d’un accès aux membres protégés de l’objet même pour lesquelles ces méthodes sont invo-quées2.

2. mais aussi par les méthodes de toutes les classes appartenant au même paqueta-ge3. Cette caractéristique est spécifique à Java: on considère que les classes du même paquetage sont vraiment des classes amies, au point de faire partie de la même famille.

Une méthode protégée peut être redéfinie. Il est possible, au moment de sa redéfinition, de modifier son mode de protection en passant du mode «protected» au mode «public». L’inverse n’est pas autorisé [Voir paragraphe - Premier corollaire: est-il possible de changer le mode de protection ? - page 83]

A titre d’illustration, considérons l'exemple suivant:

class A {

// Membres publicspublic void M1 () {

MProt();}public void M2 () {MPri() ;}public int VPub;

// Membres protégésprotected void MProt () {..}protected int VProt;

/ Membres privésprivate void MPri () {..}private int VPri;

}

1. Repris en Java, ce mode n’existait pas en Smalltalk, qui ne connaît que les modes privé et public.

2. Par exemple, si m()est une méthode protégée de la classe A, il est possible d’écrire this.m(); dans une sousclasse. Par contre, écrire x.m(); est interdit, même si x est un objet de type A.

3. En Java, un «paquetage» dénote un regroupement de classes. Notamment, les classes définies dans un même fichier appartiennent au même paquetage.

ELS - 3 février 2003

Page 76: Programmation Objet Avec Java

POO avec Java - 68 - Héritage et polymorphisme

class B extends A{

public void M2 () {..} // Redéfinition

public void M3 () { // Méthode propre à «B»

M1() ; // OK

M2() ; // OK (méthode de B)

super.M2(); // OK (méthode de a)

MProt(); // OK

VProt = 12; // OK

VPri = 12; // !! REFUSE !!

MPri(); // OK (méthode de B)

VPub = 12; // OK: Accès à la variable de B

super.VPub = 12;// OK: Accès à la variable de A

}

private void MPri () {..} // Il ne s’agit pas// d’une redéfinition !

public int VPub; // Masquage de la variable de A

}

Instanciation de la classe «B»:

B b= new B();

Exemples d'utilisation:

b.M1(); // OK:méthode de «A»

b.M2(); // OK:méthode de «B»

b.M3(); // OK:méthode de «B»

b.VPub = 12; // OK:variable de «B»

b.MProt(); // !! REFUSE!! méthode protégée de «A»

b.VProt = 12; // !! REFUSE!! variable protégée de «A»

b.MPri(); // !! REFUSE!! méthode privée de «B»

3.2.5 Le mode «paquetage »: mode par défautEn Java, par défaut, si le programmeur ne précise rien, le mode «paquetage» est pris en compte: il s’agit d’un mode de protection spécifique au langage Java ; une variante du mode privé qui autorise l'accès aux classes du même paquetage, qui sont considérées comme des classes «amies».

Notons qu’en C++, le mode «private» sera le mode par défaut.

3.2.6 Conseils et recommandationsSmalltalk ne connaît que les modes de protection «privé» et «public». En règle générale, ces deux modes suffisent. Le mode «protégé » est un luxe qui peut être ignoré par le pro-grammeur novice.

ELS - 3 février 2003

Page 77: Programmation Objet Avec Java

POO avec Java - 69 - Héritage et polymorphisme

Pour comprendre l'utilisation de ces 3 modes de protection, il est possible de faire une analogie entre la structure d'une classe et la structure d'un système d'exploitation en couches avec anneaux de protection comme Multics.

Figure 11: Analogie entre un système d'exploitation en coucheset la structure d'une classe

Le terme «API» (Application Programming Interface) désigne l'ensemble des métho-des mises à disposition par telle ou telle couche.

Les «classes étrangères» ont accès à l'API officiel.

Les «classes de la famille» ont - en plus de l'API officiel - un accès privilégié à un autre ensemble de méthode, plus confidentielles, mais aussi plus délicates à utiliser.

Concernant les variables d'instanceLà, pas d'hésitation: dans la plupart des cas, les variables d'instances seront déclarées privées, voire protégées.

En opérant ainsi, la classe sera facile à maintenir, c'est-à-dire à modifier indépendam-ment du reste du programme.

Matériel: variables d'instance privées

Noyau: méthodes privées

API documenté partiellementMéthodes protégées, accessibles uniquementpar la famille

API officiel, bien documentéMéthodes publiques

classe appartenant à la famille

classe étrangère

ELS - 3 février 2003

Page 78: Programmation Objet Avec Java

POO avec Java - 70 - Héritage et polymorphisme

En général, le gain de performance que l'on obtiendrait par un accès direct n'est pas significatif. Aussi, s’il s’agit de propriétés reconnues de l’objet, le programmeur mettra en place des «getters» et des «setters»:

private int x;

public int getX() {return x;}

public void setX(int val) {x = val;}

Il arrive toutefois qu’une classe ne soit qu’un «record» déguisé (au sens pascalien du terme), destinée uniquement à regrouper des informations dans une seule structure de données. Voici par exemple l’article Point, tel qu’il pourrait être défini en Ada:

record Point isx: integer:= 0;y: integer:= 0;

end record;

En Java, une telle structure de données sera définie en déclarant deux variables d’ins-tance publiques:

class Point {

public int x;

public int y;

public Point (int x, int y) {this.x = x;this.y = y;

}}

La classe Point est mise à disposition par la bibliothèque Java. La classe Dimension(avec ses deux variables publiques width et height) est un autre exemple.

Citons également la mise à disposition de constantes sous la forme de variables stati-ques publiques, comme par exemple les couleurs de la classe Color: Color.black, Color.white, etc.

class Color {public static final Color black =...;:

}

Concernant les méthodes

Les méthodes auxiliaires, utilitaires, seront déclarées privées.Leur utilisation par une classe extérieure pourrait avoir un effet désastreux au cas où certaines règles ne seraient pas respectées. Très proches du «matériel» (des variables d'instance), elles sont très instables et il est recommandé de les cacher.Les méthodes destinées à un usage extérieur seront déclarées:

ELS - 3 février 2003

Page 79: Programmation Objet Avec Java

POO avec Java - 71 - Héritage et polymorphisme

Protégées dans la mesure où un usage public, par les non-initiés, pourrait être dangereux, ou si cela n'avait pas de sens tout simplement.Publiques dans les autres cas.

3.3 ENCHAÎNEMENT DES CONSTRUCTEURSLa création d'un objet entraîne l'exécution automatique d'une méthode spéciale que l’on appelle le constructeur.

Le constructeur est une méthode qui sert principalement à initialiser les variables d'ins-tance. Qu'en est-il des variables d'instance héritées: comment et avec quelles valeurs sont-elles initialisées ?

C'est à cette question que répond le mécanisme « d'enchaînement des constructeurs ».

Définition 19: Enchaînement des constructeurs

Lors de la création d'un objet, l'invocation des constructeurs s'enchaîne en commençant par le constructeur de plus haut niveau: celui de la classe «Object». Ceci entraîne l'initialisation de toutes les parties héritées de la classe.

Ainsi, un constructeur commence toujours par invoquer le constructeur de la super-classe.

Cette invocation doit constituer la toute première instruction d'un constructeur.

Comment invoquer le constructeur de la superclasse ?Il suffira pour cela d’utiliser le mot-clé «super». Ainsi, pour une classe nommée «Xxx», le programmeur écrira un ou plusieurs constructeurs qui auront typiquement l'allure suivante:

public Xxx (<paramètres>) {super(<paramètres>); // Invocation du constructeur

// de la superclasse/* initialisation_des_variables_de_la_classe_Xxx */

}

L’invocation du constructeur de la superclasse doit obligatoirement constituer la toute première instruction.

3.3.1 Invocation implicite du constructeur de la superclasseDans certains cas, le programmeur peut omettre l'invocation au constructeur de la super-classe. Cette invocation sera opérée automatiquement par Java au moyen de l'instruc-

ELS - 3 février 2003

Page 80: Programmation Objet Avec Java

POO avec Java - 72 - Héritage et polymorphisme

tion:

super();

Cette omission est possible uniquement si la superclasse possède un « constructeur sans paramètre » (ce qui permet au noyau Java de l'invoquer sans avoir à réfléchir..)

Dans le cas contraire, le programmeur est tenu d'opérer une invocation explicite lui per-mettant de préciser la valeur des paramètres. Le compilateur le vérifie.

Considérons l'exemple de la classe « Gnome » qui hérite de la classe « CreatureMythique » :

class CreatureMythique extends Creature {// Variables

private int age ;private String nom ;private boolean estMagique ;

// Pour indiquer l'existence de// pouvoirs magiques

// Constructeurspublic CreatureMythique () {// 1er constructeur (sans paramètre)

this("Anonyme", 0, false);}

public CreatureMythique ( String monNom,int monAge,boolean magique) {

// Un deuxième constructeur avec paramètres

// super() ; Appel implicite au constructeur sans

//paramètre de la classe "Creature"

age = monAge ;nom = monNom ;estMagique = magique;

}::

}

class Gnome extends CreatureMythique {

private Gnome copain ; // Copain du gnome

private int taille = 1; // Taille du gnome

// Variable de classepublic static final int HAUTEUR_MAX = 3 ;

ELS - 3 février 2003

Page 81: Programmation Objet Avec Java

POO avec Java - 73 - Héritage et polymorphisme

// Constructeurspublic Gnome () {

// 1er constructeur sans paramètre// super() ; Appel implicite au constructeur// sans paramètre de la classe// "CreatureMythique"copain = null;

}

public Gnome (String nom,int age, Gnome sonCopain) {

super(nom, age, true) ;// Appel explicite du 2eme constructeur de la

// superclasse CreatureMythique

copain = sonCopain ;}::

}

3.3.2 Le constructeur par défautSi le programmeur a écrit une classe sans y avoir spécifié aucun constructeur, le compi-lateur génère automatiquement un constructeur par défaut, sans paramètre, qui ne fait rien d'autre que d'invoquer le constructeur sans paramètre de la superclasse.

Pour une classe nommée «Xxx», ce constructeur par défaut aurait l'allure suivante :

Définition 20: Allure du constructeur par défaut

public Xxx () {super(); // Invocation du constructeur sans

// paramètre de la superclasse

}

On peut se rendre compte que la génération automatique d'un constructeur par défaut n'est possible que si la superclasse dispose d'un constructeur sans paramètre.

Dans le cas contraire, le programmeur est invité par le compilateur à corriger son erreur: il doit déclarer explicitement au moins un constructeur avec ou sans paramètre.

ELS - 3 février 2003

Page 82: Programmation Objet Avec Java

POO avec Java - 74 - Héritage et polymorphisme

Définition 21: Génération d’un constructeur par défaut

A partir du moment qu’un constructeur avec ou sans paramètre est défini par le pro-grammeur, le compilateur ne génère aucun constructeur par défaut. C'est la raison pour laquelle une classe ne possède pas forcément de constructeur sans paramètre.

3.4 BIEN UTILISER L'HÉRITAGEL'héritage est un concept très puissant, mais attention toutefois; utilisé n'importe com-ment, l'héritage peut conduire à des programmes inextricables, difficiles à mettre au point et impossibles à maintenir.

Les notions décrites dans ce paragraphe sont fondamentales. Elles nécessitent une étude réfléchie. Toutefois, il est conseillé au lecteur d'avoir pratiqué assidûment le mécanisme d'héritage avant d'aborder ce qui suit.

Ce paragraphe révise un certains nombre de concepts fondamentaux, comme le concept de type. Il introduit les concepts de spécialisation et de sous-typage, et enfin, présente des règles d'utilisation de l'héritage, comme la dite règle de substitution.

3.4.1 L'héritage, vu comme un outil de spécialisationL'héritage permet de définir une classe «B» à partir d'une classe «A» existante.

class A {/* code de la classe A */

}

class B extends A {// La classe B « hérite » des propriétés de la classe A// et rajoute son propre code

/* code rajouté par la classe B */}

En principe, le but d'une sousclasse sera de spécialiser sa superclasse en augmentantsa structure et son comportement.

Utilisé dans ce sens, l'héritage permet de définir ce que l’on appelle une hiérarchie de type «Généralisation-Spécialisation».

ELS - 3 février 2003

Page 83: Programmation Objet Avec Java

POO avec Java - 75 - Héritage et polymorphisme

Figure 12: Généralisation-spécialisation

Deux exemples: les hiérarchies «Vehicule» et «Animal»

Considérons en particulier la hiérarchie «Animal» :

class Animal {private String nom;

public Animal (String nomAnimal) {nom = nomAnimal;

}

public void direQuelqueChose () {System.out.println

("je ne sais par parler..");}

}

class Oiseau extends Animal {

private boolean peutVoler = false;// Pour indiquer si l'oiseau est capable ou non de voler

public Oiseau (String nomOiseau) {super(nomOiseau);

}

public void apprendreAVoler () {peutVoler = true;

}

public boolean peutVoler() {return peutVoler;}

public void direQuelqueChose () {System.out.println ("Cui cui");

}}

Véhicule

Voiture

VoitureAEssence

Camion

Généralisation

SpécialisationVoitureAPedales

Animal

Oiseau

Roitelet

Mammifère

Perroquet Chien Baleine

Généralisation

Spécialisation

ELS - 3 février 2003

Page 84: Programmation Objet Avec Java

POO avec Java - 76 - Héritage et polymorphisme

class Perroquet extends Oiseau {

private String vocabulaire = "";// Vocable de base

public Perroquet (String nomPerroquet) {super(nomPerroquet);

}

public void nouveauMot (String unMot) {vocabulaire = vocabulaire + unMot ;

}

public void direQuelqueChose () {System.out.println (vocabulaire);

}}

Figure 13: La hiérarchie «Animal »: diagramme UML

La classe «Oiseau» spécialise la superclasse «Animal» en augmentant ses proprié-tés: deux opérations et un attribut spécifiques lui ont été rajoutés: les méthodes «apprendreAVoler» et «peutVoler», la variable d'instance «peutVoler».

Par ailleurs, cette même classe spécialise sa superclasse en redéfinissant son comporte-ment. Notamment, la fonction «direQuelqueChose» est redéfinie.

La classe «Perroquet» obéit au même principe en spécialisant sa superclasse «Oiseau».

- nom : String

+ Animal (nomAnimal: String)+ direQuelqueChose()

Animal

- peutVoler : boolean

Oiseau

+ Oiseau (nomOiseau: String)+ peutVoler(): boolean + apprendreAVoler()+ direQuelqueChose()

+ Perroquet (nomPerroquet: String)+ nouveauMot (unMot: String)+ direQuelqueChose()

Perroquet- vocabulaire : String

ELS - 3 février 2003

Page 85: Programmation Objet Avec Java

POO avec Java - 77 - Héritage et polymorphisme

3.4.2 La relation «est-un»La relation qui relie une sousclasse avec la superclasse dont elle hérite est de type «est-un» (en anglais : «is-a»).

Par exemple, on dira qu'un entier positif «est-un» entier, et qu'un entier «est-un» nom-bre. Un perroquet «est-un» oiseau, un oiseau «est-un» animal.

Un entier positif «est-une» catégorie particulière d'entiers: il s'agit d'une spécialisation dans le sens où on peut « faire plus» avec un entier positif qu'avec un entier. Par exem-ple, l'opération «racine-carrée» ne peut être opérée qu'avec un entier positif.

Attention, la relation «est-un» ne marche que dans un sens: un oiseau «est-un» animal, mais un animal «n'est-pas-un» oiseau.

Considérons par exemple la classe Animal, et ajoutons-lui la notion de « partenaire », en supposant que tout animal soit susceptible de vivre en couple avec un autre animal.

class Animal {// Variables d'instanceprivate String nom; // nom de l'animal

private Animal partenaire; // son partenaire

// Constructeurpublic Animal (String nomAnimal) {

nom = nomAnimal;partenaire = null; // Au départ,

// l'animal est seul}

// Méthodespublic void sacoquinerAvec(Animal nouveauPartenaire) {

partenaire = nouveauPartenaire;}

public void direQuelqueChose () {System.out.println("je ne sais par parler..");

}}

Utilisons cette classe:

Perroquet jacco = new Perroquet ("Jacco");Oiseau coco = new Oiseau("Coco");

jacco.sacoquinerAvec (coco) ;// paramètre de type «Oiseau»

coco.sacoquinerAvec (jacco) ;// paramètre de type «Perroquet»

Nous avons utilisé la méthode sacoquinerAvec une fois avec un paramètre de type Oiseau, et une autre fois avec un paramètre de type Perroquet.

Cette utilisation est correcte. En effet, un oiseau «est-un» animal, et un perroquet «est-un» animal également. Ces deux appels de méthodes sont donc tout à fait conformes à

ELS - 3 février 2003

Page 86: Programmation Objet Avec Java

POO avec Java - 78 - Héritage et polymorphisme

la définition de la méthode sacoquinerAvec, qui admet un paramètre de type Ani-mal.

Supposons maintenant que la méthode sacoquinerAvec ait été définie au niveau de la classe Perroquet plutôt que dans la classe Animal:

class Perroquet extends Oiseau {:public void sacoquinerAvec (Perroquet nouveauPartenaire){

partenaire = nouveauPartenaire;}:

}

Analysons les instructions suivantes:

Perroquet jacco = new Perroquet ("Jacco");Oiseau coco = new Oiseau("Coco");Perroquet jaccote = new Perroquet ("Jaccote");

jacco.sacoquinerAvec(coco);// REFUSE par le compilateur !// coco est un oiseau: ça «n'est-pas-un» perroquet

coco.sacoquinerAvec (jacco);// REFUSE par le compilateur !// «Oiseau» ne connaît pas la méthode «sacoquinerAvec»

jacco.sacoquinerAvec(cocote);// CORRECT ! !

3.4.3 Le sous-typageL'héritage introduit un concept que l'on appelle le sous-typage, concept qui sera utilisé dans la règle de substitution.

Mais, avant d'aborder cette notion, commençons par réviser la notion de type.

ELS - 3 février 2003

Page 87: Programmation Objet Avec Java

POO avec Java - 79 - Héritage et polymorphisme

Rappel : la notion de type

La classe est un outil qui permet de définir un type de données.

Considérons la déclaration suivante:

L’objet «o» est de type «T»: cela signifie que l'on attribue à «o» certaines propriétés, et que ces propriétés doivent être respectées par le programmeur qui utilisera cet objet.

Notamment:

1. les valeurs de «o» sont contraintes: «o» ne peut pas prendre n'importe qu'elle va-leur.En effet, la valeur de «o» est inscrite dans ses variables d'instance - qui sont elles-mêmes typées et donc contraintes ! - , et dont toute modification ne peut être opérée que sous le contrôle des méthodes.Par exemple, une variable d'instance de type short (entier 16 bits) est limitée aux valeurs de -32768 à +32767.

2. L’objet «o» obéit à un certain comportement qui est défini par les instructions des méthodes de la classe «T».

Notion de sous-typeL'héritage permet de créer des sous-types d'un type de base. Le type de base rassemble les propriétés générales qui sont partagées par l'ensemble des sous-types.

Un sous-type est caractérisé par de nouvelles propriétés:

des attributs supplémentaires,une fonctionnalité enrichie (par de nouvelles méthodes),certaines méthodes peuvent être redéfinies éventuellement pour tenir compte des spécificités attachées à un sous-type particulière

3.4.4 La règle de substitutionLa règle qui suit devrait être observée en toutes circonstances…

Lors d’une dérivation, c’est-à-dire de la spécification d’un sous-type «T’» à partir d’un type «T», il est vivement recommandé de ne pas altérer les propriétés du type de base «T». Notamment, toute propriété applicable à un type de base «T» doit rester applica-ble à un type dérivé «T’».

class T {....

}

T o = new T();

ELS - 3 février 2003

Page 88: Programmation Objet Avec Java

POO avec Java - 80 - Héritage et polymorphisme

Définition 22: Règle de substitution

Soient «T» un type de base, et «T'» un sous-type obtenu par dérivation de «T».

Considérons un bloc d'instructions qui s'adresse à un objet de type «T». Le même bloc d'instructions doit pouvoir s'appliquer à un objet de type «T'».

Autrement dit, il doit toujours être possible de substituer un objet de type «T'» à un objet de type «T».

L'inverse n'est pas vrai: un objet de type «T'» ne peut pas être substitué à un objet de type «T». En effet, vu que le sous-type «T'» est un type enrichi de nouvelles méthodes, un bloc d'instructions s'appliquant à «T'», et qui utilise ces nouvelles méthodes, ne pourra pas s'appliquer à «T».

3.5 LA REDÉFINITION DES MÉTHODESLa redéfinition d'une méthode consiste à reprendre la déclaration d'une méthode au ni-veau d'une sousclasse pour en donner une nouvelle implémentation.

3.5.1 Redéfinition et surcharge: parfois la confusion

Ne pas confondre redéfinition et surcharge. Si une classe héritière déclare une méthode ayant le même nom que celui d'une méthode existant dans une classe parente, il y a deux possibilités:

1. les paramètres et les type retournés sont les mêmes (en-têtes identiques): il s’agit bien d’une «redéfinition de méthode».

2. les paramètres de la méthode sont différents (signatures différentes) avec des ty-pes retournés identiques ou différents: il s'agit alors d'une «surcharge de métho-de», et non pas d'une redéfinition. Du point de vue du compilateur, les deux méthodes n’ont rien à voir l’une avec l’autre, sinon qu’elles partagent le même nom.

3. les paramètres sont les mêmes mais les types retournés sont différents: interdit

Tout ce qu'on peut faire à un animal doit pouvoir être fait avec une vache, puisqu'une vache est un animal.

Un animal n'est pas une vache ! !

On ne peut pas demander à n'importe quel animal de fabriquer du lait. Essayez donc de traire un taureau !

ELS - 3 février 2003

Page 89: Programmation Objet Avec Java

POO avec Java - 81 - Héritage et polymorphisme

par le compilateur (risque d’ambiguïté).

Un petit exemple à titre d’illustration:

class A {public int m() {

System.out.println("m original");return 0;

}public Button p() {

System.out.println("p original");return new Button();

}}

class B extends A{

public int m(int x) {// Surcharge: signature différente (paramètres différents)

System.out.println("surcharge");return 0;

}

public int m() {// Redéfinition: en-tête quasiment identique

System.out.println("redéfinition");return 0;

}

/*public float m() {

/* Interdit: ambiguïté avec la méthode précédenteVoici un exemple d’ambiguïté:B unObjet = new B();float f = unObjet.m();

Ambiguïté: cette méthode comme la précédentepeuvent être invoquées !

*/System.out.println("interdit");return 0;

}*/

public String p(int x) {// Surcharge: signature différente (paramètres différents)

System.out.println("surcharge");return "";

}

/*public Point p() {

// Interdit: ambiguïté avec la méthode définie dans ASystem.out.println("interdit");return new Point(0, 0);

}*/

}

ELS - 3 février 2003

Page 90: Programmation Objet Avec Java

POO avec Java - 82 - Héritage et polymorphisme

3.5.2 Redéfinition: répondre à la règle de substitutionLa redéfinition a pour objectif d'adapter la mise en œuvre d'une fonction logique aux particularités d'un sous-type.

C'est très bien, mais c'est une opération à risques!! avoir toujours en mémoire la règle de substitution.

Définition 23: Les «bonnes» redéfinitions

La « règle de substitution 1» doit être respectée... une redéfinition ne devrait pas modi-fier le comportement logique2 d'une méthode. Une redéfinition peut éventuellementenrichir ce comportement, mais pas l'appauvrir.

Notamment, le fait d'interdire l'usage d'une méthode3 au niveau d'une sousclasse est vraiment contraire au principe de substitution: une telle méthode ne définit pas un sous-type.

Un modèle à suivre pour la redéfinition de méthode:

void maMethode (TypeA a, TypeB b) {super.maMethode (a, b); // Invocation de la

// méthode parente...... puis, enrichissement de la méthode parente...

}

Revenons à nouveau sur l’exemple de la hiérarchie «Animal»

class Animal {

private String nom = " "; // Nom de l'animal

private boolean enForme ; // Etat de l'animal

public Animal () {} // 1er constructeur

public Animal (String nomAnimal) {//2ème Constructeur

nom = nomAnimal;enForme = true ;

}

1. Décrite précédemment2. Sans changer le comportement logique, une redéfinition peut par contre adapter la «mise

en oeuvre» de ce comportement.3. En C++, il suffit de redéfinir la méthode en mode « private ». Java interdit cette

possibilité : toute redéfinition de méthode doit être au moins aussi visible que la méthode de départ.

ELS - 3 février 2003

Page 91: Programmation Objet Avec Java

POO avec Java - 83 - Héritage et polymorphisme

public void direQuelqueChose () {System.out.println

("je ne sais par parler..");}

public void blesser() {enForme = false ; }

public boolean enForme() {return enForme ; }}

class Oiseau extends Animal {

private boolean peutVoler = false ;

public void apprendreAVoler () {peutVoler = true;

}

public boolean peutVoler() {return peutVoler;}

public void direQuelqueChose () {// Redéfinition

System.out.println ("Cui cui");}

public void blesser() {// Redéfinition

super.blesser() ;// Appel de la méthode de la superclasse

peutVoler = false ;}

}

Analyse de l’exemple:

1. Redéfinition de la méthode direQuelqueChoseIl s’agit d’une redéfinition très classique, qui se contente d’adapter le comporte-ment logique de la méthode («dire quelque chose») aux spécificités de la classe.

2. La redéfinition de la méthode blesserCette dernière fait usage du mot-clef super, qui permet d'appeler la méthode de la classe parente:Cet exemple de redéfinition est tout à fait conforme au principe de substitution : le comportement de la méthode blesser reprend celui de sa superclasse et ajoute quelques éléments spécifiques. Ainsi, s’il arrive qu’un Animal soit blessé, nous savons que cet animal ne sera plus en forme (propriété des ani-maux), même si l’animal en question est un oiseau.

3.5.3 Premier corollaire: est-il possible de changer le mode de

ELS - 3 février 2003

Page 92: Programmation Objet Avec Java

POO avec Java - 84 - Héritage et polymorphisme

protection ?

Définition 24: redéfinition et mode de protection

Pour respecter la règle de substitution, une méthode redéfinie doit être au moins aussi visible que la méthode héritée. Ainsi, la redéfinition d'une méthode publique doit être publique, et la redéfinition d'une méthode protégée sera elle-même protégée ou pub-lique1.

Rappelons par ailleurs que la redéfinition ne concerne pas les méthodes privées. La re-définition d'une méthode privée n'a évidemment pas de sens. Une méthode privée peut par contre être redéclarée au niveau d'une sousclasse. La nouvelle méthode sera com-plètement indépendante de la première.

3.5.4 Deuxième corollaire: les exceptions et la redéfinition

Définition 25: les exceptions et la redéfinition

En Java, une redéfinition n’a pas le droit de lever plus d'exceptions que sa méthode pa-rente.

Dans le cas contraire, il serait facile d’interdire l’exécution d’une méthode au niveau d’une sous-classe, ce qui serait contraire à la règle de substitution, comme par exemple:

public void m() throws InterditDusageException {throw new InterditDusageException();

}

3.5.5 Interdire la redéfinition et bloquer l’héritageCe paragraphe décrit un certains nombre de particularités qui n'appartiennent à ce jour qu'au langage Java.

Les « méthodes finales»Une méthode finale est une méthode publique ou protégée dont la redéfinition est inter-dite aux sousclasses.

1. Ainsi, interdire l'usage d'une méthode au niveau d'une sousclasse en proposant une redé-finition privée n'est pas possible

ELS - 3 février 2003

Page 93: Programmation Objet Avec Java

POO avec Java - 85 - Héritage et polymorphisme

Une telle méthode est déclarée avec le mot-clé final.

Comme par exemple

final public void m() {...}

Deux arguments à la «finalisation» d'une méthode:

1. Le premier a trait à la sécurité: on peut se fier à l'implémentation d'une méthode finale. Le comportement d'une telle méthode ne sera pas modifié quel que soit le type courant de l'objet qui reçoit le message[Voir paragraphe - Edition de liens dynamique - page 60]

2. Un deuxième concerne l'efficacité: lorsqu'une méthode non finale est invoquée, la recherche de la bonne implantation de la méthode doit être effectué pendant l'exécution. Ca n'est pas le cas des méthodes finales.

Les «classes finales»Une classe finale est définie avec le modificateur final.

Comme par exemple

final class Xxx {/* code de la classe /*

}

Une classe finale ne peut pas avoir de sousclasses. Toutes ses méthodes sont alors implicitement finales.

Les raisons pour déclarer une classe finale sont les mêmes que celles évoquées un peu plus haut: sécurité et efficacité, au dépend bien sûr de la réutilisation !

3.6 LE POLYMORPHISMELe polymorphisme est un concept qui découle de la possibilité donnée au programmeur de redéfinir des méthodes et qui s’appuie sur le mécanisme d’édition de liens dynami-que.

3.6.1 GénéralitésLe polymorphisme est un concept fondamental de la programmation objet; son applica-tion permet d'écrire des blocs d'instructions indépendants du type des objets manipu-lés.

Définition 26: Polymorphisme

Mot d'origine grec signifiant «plusieurs formes».

Le type courant de l'objet désigné par une variable ou un paramètre de méthode peut prendre plusieurs formes.

ELS - 3 février 2003

Page 94: Programmation Objet Avec Java

POO avec Java - 86 - Héritage et polymorphisme

Cette forme n'est connue qu'à l'exécution du programme, elle est susceptible de changer dans le courant même de l'exécution du programme.

Le polymorphisme s’applique principalement aux paramètres des méth-odes !

C'est en effet au niveau des paramètres de méthode que nous exploiterons au mieux le polymorphisme, comme par exemple:

public void m (X unParamètre) {/* instructions */

}

Cette méthode admet un paramètre de type «X».

Au moment de l’exécution de cette méthode, le paramètre effectif pourra prendre n'importe qu'elle forme, pour autant qu'il soit de type «X», à savoir:

1. Si «X» est une classe: le paramètre effectif peut être soit une instance de la classe «X», soit une instance de n'importe qu'elle sousclasse de «X».

2. Si «X» est une interface: le paramètre effectif peut être l’instance de toute classe qui implémente l’interface «X».

Prenons comme exemple la classe Vector de la librairie Java.

Cette classe met en œuvre les listes dynamiques de Java. En consultant cette classe, vous constaterez que les méthodes de cette classe admettent des arguments de type Object qui peuvent prendre n'importe quelle forme à l'exécution:

public void addElement(Object obj);

Ainsi, la classe Vector est une classe susceptible de contenir n'importe quel type d'élé-ment: en effet, l'argument de type Object admet des paramètres de n'importe quelle forme (rappelons que la classe Object est la superclasse de toutes les classes de Java).

Cette généricité est une exploitation typique du concept de polymorphisme.

3.6.2 Comparons Java, Smalltalk et C++Dans le cas de Smalltalk et de Java, les méthodes peuvent admettre des paramètres de type Object, les paramètres effectifs correspondants sont susceptibles de prendre n'im-porte quelle forme à l’exécution.

Avec C++, le polymorphisme n'est pas aussi poussé: la classe Object n'existe pas. En compensation, C++ offre le concept de généricité, qui permettra de déclarer des classes

ELS - 3 février 2003

Page 95: Programmation Objet Avec Java

POO avec Java - 87 - Héritage et polymorphisme

paramétrées. C’est plus compliqué à mettre en œuvre, mais ça offre toutefois la possibi-lité de contraindre le type des éléments.

ATTENTION ! Quel que soit le langage, - Smalltalk, C++ ou Java -, le type courantpourra prendre n'importe qu'elle valeur pour autant qu'il s'agisse d'un sous-type du type de base utilisé pour déclarer la variable.

3.6.3 De l'intérêt du polymorphismeLe polymorphisme est très intéressant dans la mesure où il permet de réaliser des mé-thodes générales: méthodes dont le type courant des paramètres n'est pas imposé, mais connu uniquement à l'exécution.

La possibilité de mettre en œuvre des méthodes générales permet par ricochet de créer des classes plus générales, en favorisant ainsi la réutilisation du code.

Java et Smalltalk permettent par exemple de réaliser une classe Pile, dans laquelle il sera possible d'empiler n'importe quel type d'élément. Il suffirait de définir une méthode Empiler avec un argument de type Object, qui admettra des paramètres de n'importe quelle nature.

3.6.4 Méthodes et classes abstraites Le polymorphisme ne serait rien sans les concepts de méthodes et de classes abstraites.

Pour comprendre l’utilité de ces nouveaux concepts, considérons la problématique sui-vante.

ELS - 3 février 2003

Page 96: Programmation Objet Avec Java

POO avec Java - 88 - Héritage et polymorphisme

Le problème et son cahier des charges

Supposons que le programme désire mettre en oeuvre une hiérarchie Animal répon-dant à l’architecture suivante:

Le concepteur de cette hiérarchie a prévu les contraintes suivantes dans son cahier des charges:

1. La classe Animal ne sera pas utilisée pour créer des instances. Son rôle consiste simplement à centraliser les portions de code communes aux sousclasses.

2. Tout Animal pourra répondre au message direQuelqueChose, mais ce sera aux sousclasses d’implémenter le code de cette procédure, de manière la plus ap-propriée.

3. Les programmeurs des sousclasses seront tenus de redéfinir la méthode dire-QuelqueChose.

4. De même, les programmeurs des sousclasses seront tenus de définir une métho-de seFaireMal, qui leur sera spécifique, et qui sera invoquée par la méthode blesser déclarée dans la classe Animal.

Une première implémentation

Voici une première implémentation possible, par ailleurs incorrecte comme on le verra par la suite:

class Animal {

private String nom = "Anonyme";

private boolean enForme = true;

+ Animal()+ Animal(String)+ direQuelqueChose(): void+ foncerDansLeCamarade(Animal): void+ blesser(): void

Animal

+ direQuelqueChose():void+ seFaireMal(): void

Mesange Perroquet

- nom: String- enForme: boolean

+ ajouterNouveauMot(String): void+ direQuelqueChose():void+ seFaireMal(): void

- peutVoler: boolean - vocabulaire: String

ELS - 3 février 2003

Page 97: Programmation Objet Avec Java

POO avec Java - 89 - Héritage et polymorphisme

public Animal () {} // 1er constructeur

public Animal (String nomAnimal) {nom = nomAnimal;

}

public void direQuelqueChose() {}

public void foncerDansLeCamarade (Animal unAnimal){

this.blesser()unAnimal.blesser();

}

public void blesser() {enForme = false ;this.seFaireDuMal() ;// Comportement spécifique aux sousclasses

}}

Donnons une implémentation possible de la méthode seFaireDuMal pour une Mesange et pour un Perroquet:

class Mesange extends Animal {

private boolean peutVoler;

public void seFaireDuMal() {peutVoler = false;this.direQuelqueChose();

}

public void direQuelqueChose () {System.out.println ("Cui cui");

}}

class Perroquet extends Animal {

private String vocabulaire = "";// Vocable de base

public void ajouterNouveauMot (String unMot) {// Pour rajouter un mot au vocabulaire du perroquet

vocabulaire = vocabulaire + unMot ;}

ELS - 3 février 2003

Page 98: Programmation Objet Avec Java

POO avec Java - 90 - Héritage et polymorphisme

public void faireDuMal() {this.nouveauMot ("J'ai bobo");this.direQuelqueChose() ;

}

public void direQuelqueChose () {System.out.println (vocabulaire);

}}

Un exemple d’utiliation:

Oiseau o = new Oiseau ("Jojo");Perroquet p = new Perroquet ("Jaccot");

p.foncerDansLeCamarade (o) ;

L'exécution de ce programme afficherait les deux textes suivants:

"J'ai bobo""Cui cui "

Critique de la solution

La classe Animal est refusée à la compilation: la méthode seFaireMal doit être déclarée.Le programmeur est donc conduit à déclarer la méthode suivante, avec un bloc d’instructions, tout en sachant pertinemment que cette méthode ne sera jamais utilisée:

public void seFaireMal() {}

La procédure direQuelqueChose déclarée dans la classe Animal doit com-porter un bloc d’instructions, tout en sachant pertinemment que ce bloc d’ins-tructions ne sera jamais utilisé ! (bien sûr, il s’agit d’un bloc d’instructions vide, mais la discussion porte sur le principe).Il est tout à fait possible d’instancier la classe Animal, rien ne nous en empêche Personne n’oblige les programmeurs des sousclasses à redéfinir les méthodes direQuelqueChose et seFaireMal.

Première amélioration: une classe abstraiteEn rajoutant le mot-clé abstract dans l’en-tête de la classe Animal, cette dernière de-vient une classe abstraite, qu’il est impossible d’instancier par définition.

ELS - 3 février 2003

Page 99: Programmation Objet Avec Java

POO avec Java - 91 - Héritage et polymorphisme

On répond ainsi au point no 1/ du cahier des charges.

abstract class Animal {

/* code de la classe Animal */

}

Définition 27: Classes abstraites

Une classe abstraite n'est pas destinée à la création d'instances de son propre type (opé-rateur «new» interdit).

Une classe abstraite sert uniquement à définir un cadre général, c'est à dire à définir l'ensemble des caractéristiques communes aux sousclasses qui en seront dérivées.

Par opposition, les classes instanciables sont appelées des classes concrètes.

Deuxième amélioration: des méthodes abstraitesLes méthodes direQuelqueChose et seFaireMal sont déclarées avec des blocs d’instructions vides dans la classe Animal. Ces méthodes ne seront par ailleurs jamais exécutées. Elles sont déclarées avec des blocs d’instructions uniquement pour satisfaire les exigences du compilateur.

En rajoutant le mot-clé abstract dans l’en-tête de ces deux méthodes, ces dernières deviennent des méthodes abstraites.

abstract public void direQuelqueChose();

abstract public void seFaireMal();

Le bénéfice est double !

Suppression du bloc d’instructions vide, remplacé par un simple point-virgule;Obligation faite aux programmeurs des sousclasses de fournir une implémenta-tion de ces deux méthodes. Dans le cas contraire, le compilateur considérera que leur classe est incomplète, et leur demandera à leur tour de déclarer leur classe avec le mot-clé abstract, leur interdisant ainsi d’en faire des instancia-tions.

ELS - 3 février 2003

Page 100: Programmation Objet Avec Java

POO avec Java - 92 - Héritage et polymorphisme

Définition 28: Méthodes abstraites

Une méthode abstraite est une méthode qui n'a pas de corps d'instructions.

Pour être acceptée comme telle par le compilateur, l'en-tête de la méthode doit compor-ter le mot-clé abstract (pour des questions de lisibilité).

abstract public void nomMethode (<paramètres>)

Il est possible de déclarer une classe abstraite ne contenant aucune méthode abstraite. Mais si une classe contient au moins une méthode abstraite, cette dernière devient automatiquement une classe abstraite : le compilateur oblige le programmeur à la déclarer avec le mot-clé abstract.

Une classe dérivée d’une classe abstraite est à priori elle-même abstraite, à moins qu’elle ne propose une implémentation pour toutes les méthodes abstraites qu’il reste à définir.

Une classe abstraite qui ne contient que des méthodes abstraites publiques et des constantes publiques ressemble fort à une interface. Mais son utilisation reste toute-fois différente.

3.7 MASQUAGE D’UNE VARIABLE D’INSTANCEAu contraire des méthodes, une variable ne peut pas être redéfinie.

Cependant il est possible de déclarer dans une sousclasse une variable d’instance qui aurait le même nom qu’une variable d’instance publique ou protégée définie dans une superclasse.

Dans ce cas, la variable définie au niveau de la superclasse continue d'exister, mais il n'est plus possible d'y accéder en utilisant simplement son nom: on dit que la variable est masquée.

Il est possible toutefois d'accéder à l'attribut de la superclasse en utilisant le mot-clé super.

super.nomAttribut = 10;

ELS - 3 février 2003

Page 101: Programmation Objet Avec Java

POO avec Java - 93 - Héritage et polymorphisme

A titre d’illustration, un petit exemple très tordu où le problème consiste à déterminer qui se réfère à quoi..

3.8 LE MOT-CLÉ «super»Nous avons rencontré le mot-clé super à plusieurs reprises.. voici un mémento.

Le mot-clé «super» désigne la superclasse, il peut être utilisé dans différentes situations :

super peut être utilisé dans un constructeur pour invoquer le constructeur de la superclasses .

public unConstructeur () {super (3, 4); // Invocation du constructeur

// de la superclasse:

}

super peut être utilisé également pour invoquer la version originale d'une méthode qui a été redéfinie.

class X {

protected int x;

public X (int x) {..}

}

class Xx extends X {

protected int x ;

public Xx (int x) {..}

}

class Xxx extends Xx {

protected int x ;

public Xxx (int x) {

super ( x );

x = 3;

this.x = 3;

super.x = 3;

// super.super.x = 3; SYNTAXE REFUSEE !

}}

ELS - 3 février 2003

Page 102: Programmation Objet Avec Java

POO avec Java - 94 - Héritage et polymorphisme

public void m() {// Redéfinition de la méthode «m»

super.m (); // Invocation de la méthode originale

:}

super peut enfin être utilisé pour accéder à une variable d’instance qui aurait été masquée. [Voir paragraphe - Masquage d’une variable d’instance - page 92]

super.v = x ;

3.9 VARIABLES DE CLASSE ET HÉRITAGEEn Java, une variable de classe peut être manipulée par les sousclasses comme si cette variable était déclarée dans les sousclasses elles-mêmes. Mais il n’y a pas de duplication de la variable. Il ne s’agit donc pas d’un héritage à proprement parler.

Par exemple, considérons la hiérarchie Animal Oiseau Perroquet.

On peut imaginer déclarer une variable de classe population au niveau même de la classe Animal. Cet variable, de type int, permettrait de compter le nombres d'ani-maux créés par instanciation:

class Animal {

public static int population;// Variable de classe

Animal () {// constructeur

population++; // Variable incrémentée

// à chaque création d'objet

:}:

}

class Oiseau extends Animal {::if (population > 10)...

// Manipulation de la variable

// de classe définie dans Animal

}

class Perroquet extends Oiseau {:

}

La variable population, déclarée au niveau de la classe Animal n'existe qu'en un seul exemplaire ! Elle n'est pas dupliquée dans Oiseau, ni dans la classe Perroquet.

ELS - 3 février 2003

Page 103: Programmation Objet Avec Java

POO avec Java - 95 - Héritage et polymorphisme

Déclarée public dans la classe Animal, elle est par contre visible tant au niveau de la classe Oiseau qu'au niveau de la classe Perroquet.

3.10 MÉTHODES DE CLASSE ET HÉRITAGEA l’instar des variables de classe, les méthodes de classe peuvent être manipulée par les sousclasses comme si ces méthodes était déclarées dans les sousclasses elles-mêmes. On peut parler ici d’héritage.

ELS - 3 février 2003

Page 104: Programmation Objet Avec Java

POO avec Java - 96 - Héritage et polymorphisme

ELS - 3 février 2003

Page 105: Programmation Objet Avec Java

POO avec Java - 97 - Les modèles de conception

4 Les modèles de conception

Les modèles de conception sont des éléments essentiels de la fabrication des systèmes complexes, que l’on travaille dans le domaine de l’informatique ou ailleurs.

Dans le contexte de la programmation, il faut admettre en effet que la conception d’un logiciel orienté-objet est une tâche difficile. Très souvent, la conception élaborée par le développeur sera spécifique du problème à résoudre. Parfois, - et c’est ce qui nous intéresse ici -, cette conception sera suffisamment générale pour être réutilisée et adap-table à de nouveaux problèmes.

Les concepteurs d’expérience savent qu’il faut éviter de réinventer la roue à chaque expérience, qu’il ne faut pas chercher à résoudre un problème en partant de rien. Il vaut mieux réutiliser des solutions qui ont fait leurs preuves. C’est ainsi qu’une « bonne solution » sera réutilisée systématiquement.

Nous appellerons ces solutions des modèles de conception (en anglais: «Design pat-terns»).

ELS - 3 février 2003

Page 106: Programmation Objet Avec Java

POO avec Java - 98 - Les modèles de conception

Ce chapitre est consacré à la présentation de modèles de conception fondamentaux, que l’on rencontre dans la plupart des applications.

Ce chapitre est inspiré très directement de l’excellent livre « Patterns in Java » de [Mark Grand] chez Wiley.

4.1 EN PRÉLIMINAIREAfin de présenter les différents modèles que nous rencontrerons de la manière la plus claire possible, nous tâcherons d’adopter une approche identique en articulant notre dis-cours sur les 3 particularités essentielles d’un modèle de conception:

1. Un nomOn parlera par exemple du modèle « MVC », du modèle du « Producteur-consommateur »,.. ;

2. L’énoncé du problèmeOù seront décrites la situation ou les situations dans lesquelles le modèle peut s’appliquer ;

3. La solutionQui décrit les éléments à mettre en jeux (en général, il s’agira de classes, d’objets), leurs relations et leur collaboration. Cette solution est toujours géné-rique, le programmeur aura pour tâche de l’adapter à un contexte particulier.

4.2 LE MODÈLE MVC : «MODELE-VUE-CONTRÔLEUR»A l’origine, le modèle MVC a été élaboré pendant la conception du langage Smalltalk, langage orienté-objet, développé par Xérox, Palo Alto, afin de résoudre les problèmes d’interfaçage avec l’utilisateur.

Aujourd’hui, le modèle MVC peut être utilisé pour concevoir l’architecture générale d’une application.

Ainsi, la plupart des programmes réalisés dans le cadre des travaux pratiques seront conçus en s’appuyant sur ce modèle.

Une grosse application pourrait être conçue comme un ensemble de modules qui sui-vent ce schéma.

L’objectif d’une telle architecture est de séparer et de découpler les 3 composantes d’une application :

La composante «Modèle»Structures d’informations manipulées par l’applicationLa composante «Vue»Interface avec l’utilisateur : affichage du modèle, dialoguesLa composante «Contrôleur»Contrôle général du programme, ordonnancement des opérations

ELS - 3 février 2003

Page 107: Programmation Objet Avec Java

POO avec Java - 99 - Les modèles de conception

Conçu de cette manière, un programme sera plus facile à mettre au point et à maintenir : les modifications apportées au programme seront faciles à mettre en oeuvre. Enfin, ses différents modules, fortement découplés les uns des autres, seront facilement réutilisables dans le cadre de nouvelles applications.

4.2.1 ModèleUn « modèle » est un objet qui participe à la définition des données manipulées par l’ap-plication.

Supposons par exemple une application destinée à établir et visualiser le bilan d’une école telle que l’école d’ingénieurs. Dans le « modèle » de cette application, nous trou-verons notamment les informations relatives au nombre d’inscriptions : «nbInscrits », et au nombre de diplômés : «nbDiplômés», pour chaque année.

4.2.2 VueUne « vue » est un objet participant à la réalisation de l’interface avec l’utilisateur. Il permet :

d’une part de visualiser la valeur d’un objet « modèle »,d’autre part, et le cas échéant, de modifier la valeur d’un tel objet.

Une vue est en général un objet complexe, composite, qui peut contenir des « sous-vues ».

Typiquement, une vue sera composée de graphiques, de champs d’affichage, de champs de saisie, de boutons,.. Souvent, on désigne les vues par le terme anglais « GUI » (« Graphical User Interface »).

En reprenant l’exemple présenté ci-dessus, nous pouvons imaginer que notre modèle est associé à 3 « vues », qui peuvent être présentées simultanément :

un graphique, utilisé uniquement pour visualiser le modèle pour les années 1995 à 1999 ;

un graphique, utilisé pour visualiser le modèle pour une année spécifique, offrant la possibilité d’en modifier la valeur au moyen de deux champs de saisie

inscriptions

9796 98 99

diplômés

95

ELS - 3 février 2003

Page 108: Programmation Objet Avec Java

POO avec Java - 100 - Les modèles de conception

et d’un bouton « OK » ;

un tableur, utilisé pour visualiser et pour modifier les données du modèle.

inscriptions

diplômés

1999

inscriptions

diplômés263189

OKAnnuler

263 189

OK Annuler

inscriptions diplômés

19991998199719961995 210 165

249 172

204 89232 93

ELS - 3 février 2003

Page 109: Programmation Objet Avec Java

POO avec Java - 101 - Les modèles de conception

4.2.3 Relations entre le MODELE et les VUES

Entre le modèle et ses différentes vues, MVC s’appuie sur le modèle observateur, un autre modèle de conception.

4.2.4 Le modèle «Observateur»Ce modèle décrit l’interdépendance entre un objet (le «sujet observable», ou encore «l’observé»), qui change d’état, et tous les objets qui dépendent de ce changement d’état (les «observateurs»).

Enoncé du problème

☺ Découplage..

L’objectif du modèle est de découpler autant que faire ce peut le sujet observable des observateurs. Ainsi, les classes qui représentent le sujet et les classes qui représentent les observateurs pourront être modifiées indépendamment les unes des autres et pour-ront être réutilisées séparément.

☺ Attente passive : notification par «callback»

Par ailleurs, l’objet observateur sera notifié par «callback» de l’occurrence d’un chan-gement d’état : il sera rappelé par le biais d’un envoi de message lui signalant l’événe-ment.

Etant déchargé d’avoir à interroger continuellement le sujet observable, l’observateur peut faire de l’attente passive, et libérer ainsi des ressources système. Il peut encore exécuter une activité concurrente.

263 189

OK Annuler

inscriptions diplômés

19991998199719961995 210 165

249 172204 89

232 93

inscriptions

9796 98 99

diplômés

95

inscriptions

diplômés

1999

inscriptions

diplômés

263

189

OKAnnuler

inscrits=263

dipl=189

1998

1999

inscrits=232

dipl=93

Modèle

Vues

Notification de changement

Modification

ELS - 3 février 2003

Page 110: Programmation Objet Avec Java

POO avec Java - 102 - Les modèles de conception

La solution est la suivanteLorsque le sujet observé est modifié, il diffuse des notifications à tous ses observateurs «Coucou, je suis modifié !». Il le fait de manière uniforme, sans connaître la nature des observateurs.

Les observateurs doivent au préalable avoir souscrit aux notifications, en «s’abonnant» auprès du sujet.

Une fois notifiés, les observateurs interrogent le sujet pour connaître son état.

Voici en UML, un diagramme de classes et un diagramme de séquence pour représen-ter le « modèle Observateur », adapté à MVC :

Diagramme de classe

Les différentes vues s’abonnent au modèle en lui envoyant le message «ajou-terObservateur»:leModèle.ajouterObservateur (this) ;Le modèle inscrit chaque nouvel observateur dans une liste dynamique.En réponse à toute modification de valeur (via le message «setValeur»), le modèle invoque la méthode privée «notifier», qui a pour effet de diffuser le message «mettreAJour» à tous les abonnés.Une fois notifiée, une vue peut connaître la nouvelle valeur du modèle en l’interrogeant (via «getValeur»).

Une classe «Observable» réutilisableDu point de vue mise en oeuvre, il est possible aussi de réaliser une classe «Observa-ble», responsable de la mise en oeuvre des deux méthodes «ajouterObservateur» (publique) et «notifier» (cette dernière sera privée) au moyen d’une liste dynamique dans laquelle serait enregistrée l’ensemble des observateurs.

ModelevaleurInterne : int

ajouterObservateur( : Observateur)notifier()getValeur() : intsetValeur(valeur : int)

<<Sujet>>

Vue

mettreAJour()opx()opxx()

<<Observateur>>

0..*1 0..*1

observéPar

SujetajouterObservateur( : Observateur)

<<Interface>>

ObservateurmettreAJour()

<<Interface>>

Deux interfaces, à implémenter respectivement par les classes "Modele" et "Vue"

ELS - 3 février 2003

Page 111: Programmation Objet Avec Java

POO avec Java - 103 - Les modèles de conception

Possédant une telle classe en librairie (ce qui est le cas de Java...), il est possible de l’utiliser de deux manières:

Par héritage en dérivant toutes les classes d’objets observables de la classe «Observable»;En associant notre classe d’objet observable à une instance de la classe «Observable» à qui les messages seront relayés.

Diagramme de séquence

Dans ce scénario, on voit que la vue no2 interroge le modèle pour connaître la nouvelle valeur.

4.2.5 ContrôleurLe rôle du contrôleur est de définir les réactions face aux actions de l’utilisateur (modi-fication de données, pression de bouton, sélection dans une liste, etc..).

Typiquement, un contrôleur est un algorithme qui va définir l’ordre des opérations à effectuer, qui va autoriser ou non les actions commandées par l’utilisateur, qui va agir sur l’aspect des différentes vues (en activant ou non tel ou tel bouton, etc..).

: Modele vue2 : Vuevue1 : Vue

1: ajouterObservateur(Observateur)

2: ajouterObservateur(Observateur)

4: notifier( )

5: mettreAJour( )

6: mettreAJour( )

3: setValeur(int)

7: getValeur( )

ELS - 3 février 2003

Page 112: Programmation Objet Avec Java

POO avec Java - 104 - Les modèles de conception

Il arrive fréquemment que le contrôleur soit absent du modèle MVC..

En effet, si le contrôleur se contente de jouer les intermédiaires entre le modèle et les différentes vues, sans filtrer ou sans ajouter « d’intelligence » spécifique, autant l’éliminer : on gagnera en flexibilité et en efficacité.

4.3 LE MODÈLE «OBSERVATEUR»Ce modèle décrit l’interdépendance entre un objet : le sujet observable, dont le change-ment d’état sera notifié à tous les objets qui en dépendent : les observateurs.

Ce modèle est décrit dans le cadre du modèle MVC, pour caractériser l’interdépen-dance entre le modèles et ses différentes vues.

[Voir paragraphe - Le modèle «Observateur» - page 101]

4.4 LE MODÈLE «COMPOSITE»Dans ce modèle, le composite est une structure arborescente d’objets telle que l’utilisa-teur traite de la même manière aussi bien les objets individuels (feuilles de l’arbre), que le composite lui-même.

263 189

OK Annuler

inscriptions diplômés

19991998

199719961995 210 165

249 172204 89

232 93

inscriptions

9796 98 99

diplômés

95

inscriptions

diplômés

1999

inscriptions

diplômés263

189

OKAnnuler

Modèle

Vues

Notification de changement de valeur

Contrôle de la vue (aspect, composants)

Contrôleur

Notification de changement, Action, Modification de valeur

ELS - 3 février 2003

Page 113: Programmation Objet Avec Java

POO avec Java - 105 - Les modèles de conception

Ainsi, un utilisateur qui doit agir sur un composite n’a pas à se soucier de la structure interne de ce dernier: le composite se charge lui-même de diffuser l’opération à ses composants éventuels, et ainsi de suite.

Voici une structure typique d’un tel composite :

Du point de vue de la conception d’un tel composite, les différentes classes en jeux seront organisées de la manière suivante (diagramme UML) :

On remarque que les classes du composite et des composants de base héritent toutes de la classe «Composant». Cette dernière met à disposition les opérations générales tel-les que «op1» et «op2», que l’utilisateur peut invoquer indifféremment sur un compo-sant de base que sur un composite, indépendamment de la structure interne de ce dernier.

La classe «Composite» redéfinit les opérations générales «op1» et «op2». Ces redé-finitions se contentent de diffuser «op1», respectivement «op2», à chacun des compo-sants, et ainsi de suite.

En outre, la classe «Composite» met à disposition les opérations «ajouterCompo-sant» et «retirerComposant» qui permettent de construire la structure à partir de composants (qui peuvent être eux-même des composites).

4.4.1 Un exemple: une image graphiqueVoici un exemple adapté au cas d’une image graphique, composée d’autres images gra-

Composite

Sous-composite-X ComposantDeBase-1 ComposantDeBase-2

ComposantDeBase-X1 ComposantDeBase-X2

contient

contient

1

0..*

1

0..*

contient

Composite

ajouterComposant( : Composant)retirerComposant( : Composant)op1()op2()

ComposantDeBase

Composant

op1()op2()

ELS - 3 février 2003

Page 114: Programmation Objet Avec Java

POO avec Java - 106 - Les modèles de conception

phiques (imbrication) et/ou de composants de base : des objets graphiques tels que des rectangles, des carrés, etc..

Diagramme de classe associé :

On remarque dans ce diagramme que toutes les classes correspondant aux objets gra-phiques de base ont redéfini l’opération «dessiner», qui leur est spécifique.

Image

Sous-image un rectangle un trait

un cercle un texte

contient

contient

Cercle

dessiner()

Image

ajouterComposant( : Composant)retirerComposant( : Composant)dessiner()déplacer()

ObjetGraphique

dessiner()déplacer()

1

0..*

1

0..*

Rectangle

dessiner()

Texte

dessiner()

Trait

dessiner()

ELS - 3 février 2003

Page 115: Programmation Objet Avec Java

POO avec Java - 107 - Les modèles de conception

A titre d’illustration, créons une image représentant un cyclope unijambiste:

Voici le programme Java qui permettra de construire cette image :

Cercle oeil = new Cercle(..) ; // préciser les coordonnées

Cercle formeTête = new Cercle(..) ;// dans les constructeurs

Image tête = new Image() ;tête.ajouterComposant (oeil) ;tête.ajouterComposant (formeTête) ;

Rectangle corps = new Rectangle(..) ;Trait jambe = new Trait(..);Image cyclope = new Image() ;cyclope.ajouterComposant(tête);cyclope.ajouterComposant(corps);cyclope.ajouterComposant(jambe);

cyclope.dessiner() ; // Opérations générales, diffusées

cyclope.déplacer (..) ; // à tous les composants

4.4.2 Un autre exemple : la hiérarchie des composants de la librairie «awt» de Java

En java, la construction d’une interface utilisateur s’appuie sur la librairie «awt», - Abs-tract Window Toollkit -, dont la structure est très inspirée du modèle de conception

Individu

tête un rectangle(corps)

un trait(unique jambe)

un cercle(forme)

un cercle(oeil)

ELS - 3 février 2003

Page 116: Programmation Objet Avec Java

POO avec Java - 108 - Les modèles de conception

«Composite».

Dans ce diagramme de classes, nous n’avons représenté qu’un sous-ensemble des méthodes mises à disposition par les différentes classes.

On peut remarquer la présence de la méthode «paint», qui, au niveau d’un container, sera diffusée à tous ses composants.

Cette structure permet de construire assez aisément des interfaces complexes à partir d’interfaces prédéfinis réutilisables.

O bjec t

Ca nvas

pa in t ()

C hec k box

pa in t()

L is t

pa in t ()

Tex tC om ponent

pa in t ()

S c r ollbar

pa in t()

C ho ic e

pa in t ()

Tex tA rea

pa in t ()

Tex tF ie ld

pa in t ()

C onta ine r

pa in t ()s e tLay out ()add( : C om ponent )

< < abs t rac t> >

W indow P ane l

D i alog F ram e

C om ponen tpa in t ()repa in t ()ge tG raph ic s ()s e tV is ib le ()s e tF oreground ()

< < abs t rac t> >

1

0 .. *

1

0 . . *

Gr aphi c s

s e tC o lor()s e tF ont ()d raw R ec t ()fil lR ec t ()d raw S tring()

+ c ontex te g raph ique

A pp le t

ELS - 3 février 2003

Page 117: Programmation Objet Avec Java

POO avec Java - 109 - Objets actifs

5 Objets actifs

Dans le domaine des systèmes d’exploitation, on parle souvent de systèmes d’exploita-tion dits « multi-tâches » (Windows NT, UNIX). On désigne ainsi la possibilité donnée à différents processus de s’exécuter « simultanément » sur la même machine. Dans la plupart des cas, ces processus correspondent chacun à l’exécution d’une application lo-gicielle (comme par exemple une compilation, un traitement de textes, une application Java,..).

L’objectif du système d’exploitation est de faire en sorte que tous ces processus soient isolés les uns des autres. Notamment, un processus fautif ne doit pas corrompre de manière irrémédiable le système d’exploitation, qu’il faudrait alors réinitialiser (reboot).

Avec les systèmes d’exploitation actuels, cette isolation est réalisée plus ou moins cor-rectement. Le système est malheureusement très lourd, et réclame des ressources con-sidérables. Par exemple, la communication d’informations entre deux processus est souvent complexe, peu performante, de manière à garantir qu’il n’y ait pas d’interac-tion dangereuse entre les différents partenaires.

ELS - 3 février 2003

Page 118: Programmation Objet Avec Java

POO avec Java - 110 - Objets actifs

Pourtant, de nombreuses applications « multi-tâches », composées de différentes activi-tés à exécuter simultanément, ne nécessitent pas de protection élaborée entre ces diffé-rents éléments. Au contraire, de telles applications préféreront disposer d’outils de communication efficaces entre leurs différentes unités d’exécution, quitte à prendre en charge elles-mêmes leur protection.

C’est le cas par exemple d’une petite application qui gère une ou plusieurs fenêtres sur l’écran, et en particulier un bouton «EXIT», et qui gère en parallèle une connexion réseau. La gestion du bouton et la surveillance du réseau doivent pouvoir se dérouler de manière simultanée et indépendante, au moyen de deux «processus» qui s’exécute-ront à l’intérieur de la même application.

5.1 GÉNÉRALITÉS SUR LES THREADSLes « threads » remplissent exactement ce rôle : ils permettent à une application de dé-finir plusieurs unités d’exécution se déroulant en concurrence à l’intérieur du même processus

La notion de «thread»Un thread est donc une espèce de «processus à l’intérieur du processus».

En français, on parlera «d’activité», «d’unité d’exécution», ou simplement de «tâche»… Comme la terminologie n’est pas vraiment établie, nous nous contenterons du terme anglais « thread ».

En opposition aux processus, les threads sont parfois appelés « light-weight processes » : des « processus légers ». En effet, les threads se distinguent des proces-sus par les propriétés suivantes :

1. Ils disposent d’un espace de mémoire partagé : les threads liés à une même ap-plication s’exécutent tous dans le même espace d’adressage et ils peuvent donc communiquer tout simplement par la mémoire. Concrètement, en Java, cela si-gnifie que deux threads peuvent communiquer de l’information au travers d’un objet qui leur sera associé, ou au travers de variables d’instance.Au contraire, deux processus seront isolés l’un de l’autre. Le système d’exploitation veillera à ce que ni l’un ni l’autre ne puisse accéder à l’espace d’adressage de l’autre ;

2. Deux threads peuvent partager les mêmes instructions (partage de code) ;3. Deux threads peuvent partager les mêmes ressources. Par exemple, deux

threads peuvent accéder simultanément au même fichier qui n’aura été ouvert qu’une seule fois. Au contraire, les processus disposent chacun d’un lot de res-sources qui leur sont allouées personnellement par le système d’exploitation. Ils peuvent bien sûr partager ces ressources entre eux, mais avec l’accord du systè-me d’exploitation ; en outre ce partage sera toujours opéré sous le contrôle du système, ce qui est lourd et peu performant.

4. Les threads ne sont pas protégés les uns des autres : si l’un d’eux corrompt l’ap-plication, c’est toute l’application (et tous les autres threads) qui seront corrom-pus.

ELS - 3 février 2003

Page 119: Programmation Objet Avec Java

POO avec Java - 111 - Objets actifs

Pourquoi utiliser plusieurs threads dans la même application ?Dès qu’une application doit gérer des événements provenant de plusieurs sources dif-férentes, et que la gestion de chacun de ces événements doit être opérée de manière in-dépendante et concurrente, le programmeur aura tout intérêt à confier le traitement de chacune de ces sources d’événements à un thread particulier.

A titre d’illustration, on peut montrer l’interaction d’une application confrontée à deux sources d’événements : des événements de l’interface utilisateur et des événements réseau. Les deux threads correspondant communiqueront entre eux, et avec l’applica-tion, au moyen d’objets déclarés à l’intérieur de l’application.

Figure 14: Une application «multi-threaded»

Un exemple en Java : le lièvre et la tortue En Java, un thread est manipulé sous la forme d’un objet qui exécute les instructions d’une méthode particulière : la méthode run().

En tant qu’objet, un thread est susceptible de recevoir des messages : start(), set-Priority(), join(), etc.

Internet

Souris

Thread,Interface

Utilisateur

Thread,Connexion

réseau

Clavier

GestionWindows NT

Application, composée de threads concurrents

Objets decommunication

Application

Socket

ELS - 3 février 2003

Page 120: Programmation Objet Avec Java

POO avec Java - 112 - Objets actifs

Pour créer de tels objets, Java propose la classe Thread qui met en ouvre toute la mécanique de gestion des activités. En outre, cette même classe définit une méthode run() qui, par défaut, ne fait rien.

Le programme présenté ci-dessous met en jeux un lièvre et une tortue, deux objets de type Thread. Ces deux objets sont instanciés à partir de la classe Animal, classe dérivée de Thread, et dans laquelle se trouve redéfinie la méthode run() qui sera exécutée simultanément par le lièvre et la tortue.

Le lièvre affiche des « L » toutes les 20 msec et la tortue des « T » toutes les 100 msec.

Lequel arrive en premier ?

class Animal extends Thread {// Deux variables d'instanceprivate String nom; // Nom de l'animal

private int période; // Période entre deux affichages

public Animal (String nom, int période) {this.période = période; this.nom = nom;

}

public void run() {// Code de l'activité

int distance = 10; // Distance à parcourir

while (distance > 0) {try {Thread.sleep(période);}// Pause: l'animal court..catch(InterruptedException e) {}System.out.println (nom);distance--;

}}

}

public class LievreEtTortue {public static void main (String arg[]) {

System.out.println("Le lievre et la tortue");

Animal lièvre = new Animal ("L**", 20);Animal tortue = new Animal ("T", 100);tortue.start(); // Démarrer les activités

lièvre.start();

try {int j=0; j = System.in.read();}catch (IOException e){};// Attendre RETURN

}}

ELS - 3 février 2003

Page 121: Programmation Objet Avec Java

POO avec Java - 113 - Objets actifs

Dans cet exemple, les deux threads se partagent la ressource «écran d’affichage». Ils se partagent aussi le code de la classe Animal. Ce partage aurait difficile à opérer en uti-lisant des processus (les 2 fenêtres auraient été séparées, duplication du code), sans parler de la perte d’efficacité au niveau de la rapidité d’exécution.

5.1.1 Concurrence ou parallélisme ?Précisons les notions de concurrence et de parallélisme.Les systèmes concurrents donnent simplement l’illusion d’avoir plusieurs activités qui s’exécutent en même temps. En fait, toutes ces activités se partagent l’unique proces-seur de la machine par un entrelacement de leur exécution.

Les systèmes parallèles sont capables d’exécuter simultanément plusieurs activités. A cette fin, ces systèmes disposent de plusieurs processeurs, responsables chacun de l’exécution d’une activité.

ELS - 3 février 2003

Page 122: Programmation Objet Avec Java

POO avec Java - 114 - Objets actifs

Figure 15: Vrai parallélisme et «pseudo-parallélisme»

A moins que les activités ne passent leur temps à attendre la fin d’une opération d’entrée/sortie, un système qui exécute des activités de manière concurrente (par entre-lacement) sera plus lent qu’un système qui les exécutera de manière séquentielle. En effet, la commutation entre deux activités concurrentes (changement de contexte), prend du temps. La concurrence améliore l’efficacité du système dans la mesure où les activités qui entrent en jeux présentent plutôt un caractère «IO-bound», c’est-à-dire quand elles opèrent beaucoup d’entrée/sorties.

5.1.2 Mise en oeuvre des threads en JavaA l’heure actuelle, la machine virtuelle Java travaille de manière concurrente, le pa-rallélisme n’est pas prévu. Notamment, aucun mécanisme n’est prévu pour assigner tel ou tel processeur physique à telle ou telle activité. Toutefois, à l’avenir, les choses peuvent changer...

En attendant cette éventualité, les concepteurs de Java ont décidé d’exploiter le parallé-lisme offert par certaines plateformes. C’est le cas par exemple de Windows NT qui est capable d’exécuter plusieurs threads de manière simultanée sur plusieurs processeurs.

Ainsi, plutôt que d’implémenter la concurrence des threads entièrement dans la machine virtuelle, les concepteurs ont choisi de s’appuyer sur la mécanique sous-jacente mise à disposition par le système d’exploitation. C’est ainsi qu’il est possible, sous Windows NT, que plusieurs threads de votre application s’exécutent simultané-ment. En implémentant le mécanisme entièrement dans la machine virtuelle, il est fort probable que le système d’exploitation n’exécute l’application (vue comme un seul thread) que sur un seul et unique processeur.

Cette façon d’avoir envisagé les choses présente un gros inconvénient : à moins que l’on y prenne garde, les applications Java ne sont plus portables d’un système à

ParallélismeConcurrence

Tâche 1Tâche 2

temps temps

Tâche 1 Tâche 2

Séquentiel

temps

Tâche 1

Tâche 2

ELS - 3 février 2003

Page 123: Programmation Objet Avec Java

POO avec Java - 115 - Objets actifs

l’autre. Certaines caractéristiques, – comme le «time-slicing» notamment –, n’appa-raîtront que sur certains systèmes.

En dehors de ça, Java propose une implémentation des threads qui essaye, autant que possible, d’être indépendante du système d’exploitation. Ainsi, la politique d’allocation du processeur sera définie par un certain nombre de principes ou de règles qui devront être observées par les différentes implantations de la machine virtuelle.

5.2 LES THREADS DE LA MACHINE VIRTUELLEL’environnement Java s’intègre entièrement dans un espace «multi-threaded». Ainsi, par exemple, toute applet s’exécutera dans son propre thread, pour télécharger des ima-ges, plusieurs threads séparés seront utilisés, le «garbage collector» lui-même s’exécu-tera dans son propre thread, etc...

ELS - 3 février 2003

Page 124: Programmation Objet Avec Java

POO avec Java - 116 - Objets actifs

Voici, en résumé, la liste des threads qui s’exécuteront dans toute application autonome Java ou dans toute applet:

Nom du thread Description

Clock handler Un thread qui prend en compte et assure la ges-tion de toutes les invocations des méthodes sleep(), wait(..) et repaint(int).

Idle thread Un de priorité «0», qui ne s’exécute que si tous les autres threads sont bloqués

L’exécution de ce thread indique au « garbage collector » qu’il peut effectuer sont travail sans ralentir l’application (puisqu’il n’y a rien à faire).

Garbage collector Le ramasse miettes, qui libère la mémoire de tous les objets qui ne sont plus référencés.Ce thread ne s’exécute qu’une fois par seconde. Il teste alors si le thread « Idle » s’est exécuté. Si c’est le cas, il suppose que l’application est passive, et commence son travail de libération. Si ça n’est pas le cas, il se rendort pour une seconde.Ce thread s’exécute en priorité «1».

Finalyser thread Un thread «démon», qui exécute la méthode «finalise()» des objets libérés.Ce thread s’exécute en priorité «1».

Main Ce thread exécute la méthode main() des applications autonomes.

AWT_Input,

AWT-Toolkit(AWT-Motif ou AWT-Win-dows)

Ces threads prennent en charge les entrées et les événements détectés par le système de fenê-trage du système d’exploitation sous-jacent.

Screen updater Collecte et rassemble toutes les requêtes repaint() et génère des appels paint().

Image fetcher Quatre threads séparés responsable du charge-ment d’images depuis le réseau.

Audio player Pour « jouer » les sorties audio.

AppletName Le thread de l’applet, celui qui appelle les méthodes start(), stop(),..

ELS - 3 février 2003

Page 125: Programmation Objet Avec Java

POO avec Java - 117 - Objets actifs

5.3 DIAGRAMME D’ÉTATS ET DE TRANSITIONSComme nous l’avons vu précédemment, un thread est implanté sous la forme d’un objet, susceptible de répondre à différents messages : «start()», «setPriority()», etc…

Le diagramme d’états et de transitions présenté à la figure 16, page 118, nous donnera une première idée de la logique de mise en œuvre des threads en Java.

Construction d’un threadPassage à l’état Construit.

L’objet vient d’être créé par l’opérateur new, mais le thread est «dans les limbes» : il n’est pas encore exécutable. Il faudra pour cela lui envoyer le message start().

Lancement d’un thread: start()Passage à l’état Actif.

La méthode run() a été lancée (le message start()a été reçu).

Le thread fait alors partie des threads qui ont le droit de s'exécuter: le thread alterne entre les états «Prêt» et «En exécution», en fonction de la politique d'ordonnancement des threads.

C’est le «scheduler» (l’ordonnanceur) qui peut faire revenir le thread de l’état «En exé-cution» à l’état «Prêt».

ELS - 3 février 2003

Page 126: Programmation Objet Avec Java

POO avec Java - 118 - Objets actifs

Figure 16: Threads: diagramme d'états et de transitions

Figure 17: L'état «prêt»

Pour plus de détails [Voir paragraphe - Scheduling: politique d’allocation du processeur - page 124]

Début:

Terminé (mort)Construit

En exécution

Actif

start()

schedulingPrêt(exécutable)

Inactif(bloqué)

new Thread(..)

Endormi

suspend()

sleep(msec)

Fin Entrée/Sortie

resume()

Pause terminée

Opération Entrée/Sortie

run() a terminé son exécution(terminaison normale)

stop()

stop()

stop()

Endormi Endormi

En exécutionPrêt(exécutable)

. préemption (au profit d'un thread "prêt" de plus haute priorité)

. tranche de temps écoulée ("time slice")

. yield() (le thread abandonne de lui-même le processeur)

sélection du threadde plus haute priorité

ELS - 3 février 2003

Page 127: Programmation Objet Avec Java

POO avec Java - 119 - Objets actifs

Blocage d’un thread (sleep(), suspend() et entrée-sortie)

Note préliminaire: la méthode «suspend()» est éliminée de la version Java 1.2. [Voir paragraphe - Les méthodes «stop()», «suspend()» et «resume()» sont dépréciées dans java 1.2- page 143].

Passage à l’état «inactif».

On notera qu’à chaque cas de blocage (d’endormissement) correspond une et une solu-tion pour le réveil (passage à l’état « Actif »).

sleep(..) fin de la pausesuspend () réception du message «resume()»Entrée/Sortie opération terminée

(p.e : System.in.read() ;

Voici un exemple de suspension d’un thread: la bavarde Hildegarde..

class Bavarde extends Thread{public void run () {

while (true) {afficher ("Blabla") ;

}}

}

Bavarde Hildegarde = new Bavarde() ;Hildegarde.start () ;

afficher ("Hildegarde, " ) ;afficher ("nous avons ta mere et moi, " ) ;afficher ("Qqch a te dire ") ;afficher ("Hildegarde veux-tu bien m’ecouter ? ") ;

Hildegarde.suspend() ;afficher ("nous avons decide de te marier " ) ;afficher ("J’espere que tu es contente ") ;Hildegarde.resume() ;

ELS - 3 février 2003

Page 128: Programmation Objet Avec Java

POO avec Java - 120 - Objets actifs

Dans la mesure où la méthode «afficher» suspend la tâche courante le temps que l'opération d'entrée-sortie soit terminée, nous aurons l'affichage suivant:

Hildegarde,BlaBlanous avons ta mere et moi,BlaBlaQqch a te direBlaBlaHildegarde veux-tu bien m’ecouter ?BlaBlanous avons decide de te marierJ’espere que tu es contenteBlaBlaBlaBla…

Mort d’un thread

Note préliminaire: la méthode «stop()» est éliminée de la version 1.2 de Java. [Voir paragraphe - Les méthodes «stop()», «suspend()» et «resume()» sont dépréciées dans java 1.2- page 143]

La mort d’un thread survient après deux occasions:

fin de la méthode «run ()» le thread se suicide …

ou la réception du message «stop()» le thread est assassiné (solution simple et radicale, mais peu recommandée

en programmation)

Supposons maintenant que le père soit vraiment fatigué des bavardages de sa fille :

Hildegarde.stop() ;System.out.println (" Enfin la paix ! ") ;

Récupération d’un thread «mort» par le garbage collectorPour être récupéré par le garbage collector, le fait que le thread ait terminé son exécution ne suffit pas. Il faut encore que l’objet qui le représente ait été déréférencé.

ELS - 3 février 2003

Page 129: Programmation Objet Avec Java

POO avec Java - 121 - Objets actifs

Le cas échéant, il suffit d’opérer cette opération manuellement :

activité = null ;

Le système de gestion des threads s’occupera alors lui-même de libérer toutes les res-sources allouées au thread.

Au cas où l’objet représentant le thread est déférencé sans que le thread ne soit mort, que l’on se rassure : le thread ne sera pas détruit pour autant. Le système de gestion des threads conserve en effet une référence sur tous les threads actifs.

Contrôler l’état d’un autre thread: isAlive()La méthode isAlive() retourne true dans tous les cas où le thread est dans l’un ou l’autre des deux états Actif ou Inactif.

Attendre la terminaison d’un thread: join()Le message join() envoyé à un thread «t», bloque le thread courant en attendant la terminaison du thread «t».

Le thread courant est réveillé aussitôt que le thread «t» passe à l’état Terminé (mort).

5.4 RÉALISER DES OBJETS ACTIFS EN JAVAVis à vis de la concurrence, la méthodologie Objet distingue deux types d’objets (selon [Booch]) : les objets passifs et les objets actifs.

Les objets passifsLe code de ces objets s’exécute uniquement en réponse à un envoi de message extérieur.

Figure 18: Un objet passif.

Le code de la méthode invoquée par le message extérieur s’exécute à l’intérieur du thread de l’objet qui a envoyé le message. Un tel objet n’est pas capable de changer d’état par lui-même, il ne pourra le faire qu’en répondant à un message extérieur.

un objet passif

une méthodemessages envoyés par l'objet, en réponse au message reçu

message extérieur

ELS - 3 février 2003

Page 130: Programmation Objet Avec Java

POO avec Java - 122 - Objets actifs

Le objets actifsCes derniers sont associés à une activité qui leur est propre, et qui s’exécute concurrem-ment au reste du programme.

Ces objets sont donc capables de changer d’état par eux-mêmes. Ils sont en outre géné-rateurs de messages.

Figure 19: Un objet actif

Réalisation d’un «objet actif» en JavaEn gros, Java prévoit deux manières d’opérer.

Méthode no1 L’objet actif « est-un » thread, c’est-à-dire une instance d’une sousclasse de «Thread».Méthode no2 (la plus générale, la meilleure..)L’objet actif est associé à un thread, qui sera généralement déclaré sous la forme d’une variable d’instance de l’objet actif.

Cette méthode est meilleure pour deux raisons :

elle est plus naturelle du point de vue de la méthodologie objet,l’objet actif peut hériter d’autre chose que de la classe «Thread» (rap-pelons que Java ne connaît pas l’héritage multiple).

A titre d’exemple, nous allons déclarer des bavards qui passeront leur temps à afficher un texte (toujours le même) à intervalles de temps réguliers.

un objet actif

une méthodemessages envoyés par l'objet, en réponse au message reçu

message extérieur

thread associé

messages envoyés par l'activité propre à l'objet

ELS - 3 février 2003

Page 131: Programmation Objet Avec Java

POO avec Java - 123 - Objets actifs

Méthode no1: par dérivation de la classe «Thread»L’objet actif « est-un » thread..

class Bavard extends Thread{private String texte;private int pause;

public Bavard (String txt, int pause) {texte = txt; this.pause = pause;

}public void run () {

while (true) {try {Thread.sleep(pause); }catch (InterruptedException e) {}System.out.println(texte);

}}

}

public class Bavards {public static void main (String arg[]) {

new Bavard ("ttt", 30).start();new Bavard ("xxxxx", 100).start();

}}

L’inconvénient de cette méthode est que la classe «Bavard» hérite au départ de la classe «Thread». Elle ne peut donc pas hériter d’une autre classe.

ELS - 3 février 2003

Page 132: Programmation Objet Avec Java

POO avec Java - 124 - Objets actifs

Méthode no2: par implémentation de l'interface «Runnable»L'objet actif est associé à un thread..

Pour essayer ce « bavard » :Bavard unBavard = new Bavard ("tttt", 30) ;unBavard.démarrer() ;

Cette méthode consiste à réaliser une classe qui implémente l'interface «Runnable». Cette interface est défini comme suit:

public interface Runnable {public abstract void run();

}

5.5 SCHEDULING: POLITIQUE D’ALLOCATION DU PROCESSEURJava doit pouvoir s’exécuter à la fois sur des machines monoprocesseurs et multiproces-seurs, que le système d’exploitation soit ou non «multi-threaded». C’est pourquoi les règles relatives aux threads sont générales.

5.5.1 La priorité des threadsJava attache une priorité à chaque thread. La priorité d’un thread est initialement la

class Bavard implements Runnable{private String texte;private int pause;

private Thread activité;

public Bavard (String txt, int pause) {texte = txt; this.pause = pause;

activité = new Thread (this);

activité.start();

}

public void run () {while (true) {

try {Thread.sleep(pause); }catch (InterruptedException e) {}System.out.println(texte);

}}

public void démarrer() {activité.start();}public void arrêter() {activité.stop();}

}

Construction du thread."this" indique que lethread doit exécuterla méthode "run()" de la classe "Bavard".Le type de ce paramètredoit être de type "Runnable"

Pour démarrer lethread (ici ou ailleurs)

Méthodes éventuelles,pour contrôler l'activitédepuis l'extérieur

La classe "Bavard" s'engage àimplémenter la méthode "run()"

Déclaration d'une activité associée

ELS - 3 février 2003

Page 133: Programmation Objet Avec Java

POO avec Java - 125 - Objets actifs

même que la priorité du thread qui l’a créé.

La priorité standard pour un thread vaut par défaut Thread.NORM_PRIORITY.

Cette priorité peut être changée en utilisant la méthode setPriority avec une valeur comprise entre les constantes Thread.MIN_PRIORITY et Thread.MAX_PRIORITY.

Ces constantes valent respectivement «5»: la valeur par défaut, «1»: minimum et «10»: maximum.

Dans le cas particulier des applets, la priorité maximum est limitée à NORM_PRIORITY+1, c’est-à-dire à «6».

Note d’implémentationLa mise en œuvre des priorités s’appuie sur le système d’exploitation sous-jacent.

SUN Solaris : le système prévoit pour sa part 232 niveaux de priorités. La mise en œuvre des 10 priorités de Java ne pose aucun problème.

Windows NT : le système alloue une plage de priorités limitée à 7 niveaux ! Com-ment les 10 priorités de Java sont-elles mises en correspondance avec ces 7 niveaux ? ça n’est pas précisé…

Il est donc fortement conseillé, -sous Windows NT-, de ne pas construire son système d’ordonnancement sur la base des priorités. Nous pouvons être sûr tout au moins que les 3 priorités «NORM_PRIORITY», «MIN_PRIORITY» et «MAX_PRIORITY» sont distinguées.

5.5.2 Règles d’ordonnancement des threadsVoici les règles d’ordonnancement garanties par Java et sur lesquelles pourront s’ap-puyer les programmeurs.

ELS - 3 février 2003

Page 134: Programmation Objet Avec Java

POO avec Java - 126 - Objets actifs

Figure 20: Règles d'ordonnancement

• Tant que le thread de plus haute priorité n’est pas endormi, c’est lui qui a la priorité.Il y a donc préemption. Supposons qu’un thread passe à l’état « Prêt » alors qu’un thread de plus faible priorité est « En exécution » ; on n’attendra pas la fin de l’exécution du thread actif : le processeur lui est retiré et il est rajouté dans la queue des threads «Prêt».

• Entre deux threads de même priorité, ça dépend de la machine...Java, pour son compte, ne donne aucune spécification . S’il faut choisir entre deux threads de même priorité pour occuper le processeur, c’est le hasard qui fera son choix, indépendamment du fait que l’un des deux threads a attendu plus longtemps que l’autre à l’état «Prêt».

• Partage du temps entre threads de même priorité («time slicing»)Java ne précise pas non plus s’il y a ou non une préemption sur la base d’une tranche de temps entre threads de même priorité, et si oui, si la queue des threads de même priorité est alors organisée en tourniquet…

Note d’implémentation : organisation des queues de prioritéPour ce qui est des deux derniers points, Java s’appuie sur le système d’exploitation sous-jacent.

Windows : ce dernier organisera les threads à l’état « Prêt » en plusieurs queues, une par priorité. Au sein d’une queue de même priorité, les threads seront organisés selon un schéma FIFO.

Note d’implémentation: le «time-slicing»Windows : à l’heure actuelle l’implémentation s’appuie sur le mécanisme multi-threa-ded du système sous-jacent. Ainsi, sous Windows NT et Windows 95, on dispose d’un système « temps-partagé » qui alloue le processeur par tranches de temps. L’exécution

En exécutionPrêt(exécutable)

. préemption (au profit d'un thread "prêt" de plus haute priorité)

. tranche de temps écoulée ("time slice")

. yield() (le thread abandonne de lui-même le processeur)

sélection du threadde plus haute priorité

endormi

RéveilBlocage

ELS - 3 février 2003

Page 135: Programmation Objet Avec Java

POO avec Java - 127 - Objets actifs

des threads de même priorité s’effectue à la manière d’un tourniquet (« round robin »). Chaque priorité est associée à une queue de threads à l’état « Prêt ». Chacune de ces queues est organisée en FIFO : les threads s’exécutent ainsi à tour de rôle.

SUN-Solaris: l’implémentation d’un système de temps partagé n’existe pas, et n’est pas prévu à ce jour.

La solution au problème de portabilité qui peut alors se poser est d’utiliser l’opération yield(), qui autorise un thread à abandonner le processeur au profit d’un thread de même priorité.

5.5.3 Le modèle préemptif de Java

Définition 29: Le modèle préemptif de Java

Le modèle d’ordonnancement de Java est dit «préemptif».

L’exécution du thread courant peut être interrompue à tout moment au profit d’un thread se trouvant dans l’état «Prêt».

La préemption peut arriver à 4 occasions :

1. quand le thread courant se bloque : entrée-sortie, «sleep()», «sus-pend()»,..

2. quand le thread courant « donne la main » («yield()») ;3. quand un thread de plus haute priorité arrive à l’état « Prêt » ;4. à l’expiration d’une tranche de temps (time-slicing).

Le point 4/ (time-slicing) introduit une forme de non-déterminisme dans l’ordre d’exé-cution des threads. Si le programmeur désire que ses threads s’exécutent selon un ordonnancement précis, il doit s’en assurer en utilisant les mécanisme de synchronisa-tion ( wait, notify,..)

Voici par exemple 3 «bavards» qui affichent chacun 10 fois une lettre de l’alphabet qui leur est propre :

ELS - 3 février 2003

Page 136: Programmation Objet Avec Java

POO avec Java - 128 - Objets actifs

class Bavard extends Thread {private String monSigle;

public Bavard (int priorité, String monSigle) {setPriority (priorité) ;this.monSigle = monSigle;

}

public void run () {for (int cpt = 1 ; cpt <= 3 ; cpt++) {

System.out.println (monSigle + " " ) ;yield() ;

}}

}

public class LesBavards {public static void main (String arg[]) {

new Bavard (5, "xxxxx").start();new Bavard (3, "ttt").start();for (int cpt = 1 ; cpt <= 3 ; cpt++) {

System.out.println("Thread par défaut") ;

yield() ;}

}}

Cet exemple comporte 3 threads : le «thread par défaut», qui correspond à l’exécution de «main» (priorité 5) et deux bavards, de priorité respective 3 et 5.

Exécution du programme (sous Windows NT ou SUN Solaris) :Thread par défautxxxxxThread par défautXxxxxThread par défautXxxxxttttttttt

L’exécution du «thread par défaut» et du bavard «xxxx» s’alternent : ils se donnent la main avec yield(), et ils ont par ailleurs la même priorité. Comme la priorité du bavard «ttt» est la plus faible, il s’exécute après les deux autres.

Note d’implémentation Sous Windows NT, toutefois, à l’exécution, on pourra constater que si l’écart entre les

ELS - 3 février 2003

Page 137: Programmation Objet Avec Java

POO avec Java - 129 - Objets actifs

priorités des deux bavards est égal à 1 (comme par exemple «3» et «4»), l’exécution des threads sera parfaitement entrelacée : leur priorités sont donc considérées comme iden-tiques. La différence de priorité n’est prise en compte que si l’écart vaut 2 au minimum.

Le risque de faminePour compliquer le tout, Java trouve judicieux de garantir qu’un jour ou l’autre, un thread de basse priorité finira par s’exécuter, de manière à éviter le risque de famine (« starvation »).

Cela signifie qu’au sein de la queue des processus « Prêt », ça n’est pas forcément le thread de plus haute priorité a qui le processeur est confié…

5.5.4 En conclusionPour écrire une application Java portable, il nous faut prendre en compte certaines con-sidérations.

Comme nous ne pouvons faire aucune supposition quant au système d’exploitation sous-jacent, le programme doit être conçu pour fonctionner dans le pire des cas..

Notamment, il faut s’attendre à ce que nos threads soient interrompus à tout moment (si le système implémente un «time-slice»). Il nous faut donc utiliser le verrou de syn-chronisation mutuelle (synchronized).

Il faut s’attendre également au contraire, à ce que nos threads ne soient jamais inter-rompus. Il nous faut donc utiliser des méthodes comme sleep(..) ou yield() de manière à abandonner le processeur au profit des autres.

On ne peut pas non plus supposer que les niveaux de priorité 1 et 2 soient différents puisque les 10 niveaux de priorité de Java sont mis en correspondance avec 7 seulement sous NT.

5.6 EXCLUSION MUTUELLE: VERROUILLAGE D’UN OBJET

Dans cette partie du document, nous allons étudier comment éviter les problèmes liés au « hasard de l’ordonnancement », comment coordonner l’accès à l’information, et enfin comment synchroniser les threads de manière générale.

Définition 30: Notion de moniteur

Nous verrons que la synchronisation en Java est basée sur la technique des « moniteurs » de C.A.R Hoare. Un moniteur est un bout de code dont l’accès exclusif est garantit par un «verrou d’exclusion mutuelle»: un seul thread à la fois sera en pos-session du verrou, ce qui lui donnera la possibilité d’exécuter les instructions du moni-teur.

ELS - 3 février 2003

Page 138: Programmation Objet Avec Java

POO avec Java - 130 - Objets actifs

Nous allons illustrer le problème avec l’exemple classique qui suit.

Supposons que l’on dispose d’un distributeur de billets de banque qui permette de faire des retraits sur un compte bancaire particulier.

L’algorithme réalisé par le distributeur est le suivant :

1. vérifier que le solde du compte est suffisant pour autoriser le retrait,2. mettre à jour l’état du compte (nouveau solde),3. distribuer les billets,4. imprimer un reçu

Voici le code de cet algorithme en Java :public class Distributeur {

public void retirer (int montant) {CompteBancaire cb = getCompte() ;if (cb.débiter(montant)) {

distribuer(montant) ;imprimerReçu() ;

}}

:}

public class CompteBancaire {private int solde ;public boolean débiter (int montant) {

if (solde – montant >= 0) {solde -= montant ;return true ;

}return false ;

}}

En règle générale, cet algorithme fonctionnera très bien jusqu’au jour ou deux person-nes voudront opérer un retrait sur le même compte simultanément. On peut supposer par exemple qu’il s’agit d’un « compte-joint » partagé par un couple.

Avec le « hasard de l’ordonnancement », il est possible (malgré le contrôle) que l’argent soit distribué aux deux parties, et que l’on se retrouve avec un solde négatif !

Supposons en effet que les opérations soient accomplies dans cet ordre :

1. le thread du mari commence l’exécution de la méthode «débiter» , et il est

ELS - 3 février 2003

Page 139: Programmation Objet Avec Java

POO avec Java - 131 - Objets actifs

vérifié que le solde est supérieur ou égal au montant à débiter ;public boolean débiter (int montant) {

if (solde – montant >= 0) {*--------------------- préemptionsolde -= montant ;return true ;

}return false ;

}

2. Préemption1 : le thread de la femme prend le contrôle du processeur et com-mence l’exécution de la méthode «débiter »; comme le montant du mari n’a pas encore été débité, le retrait est accepté, la méthode retourne «true» et l’ar-gent est distribué ;

3. Le thread du mari reprend le contrôle du processeur : la méthode débiter conti-nue son exécution (entre temps, la valeur du solde a été modifiée) et l’argent est distribué, même si le solde est passé en négatif !

Une solution simple pour ce problème consiste à « atomiser » la méthode «débiter».

Définition 31: Méthode atomique

Une méthode atomique ne peut pas être exécutée par deux threads simultanément. Pra-tiquement, une telle méthode n’est pas « décomposable » : elle ne peut pas être inter-rompue en son milieu pour être exécutée par un autre thread.

En utilisant une autre terminologie, on dira que le code d’une méthode atomique s’ins-crit dans une section critique, c’est-à-dire une section qui ne peut être exécutée que par un seul thread à la fois.

Le mot-clé synchronizedEn Java, la mise en oeuvre d’une méthode atomique s’opère très simplement au moyen du mot-clé «synchronized». A lui seul, il garantit que cette méthode ne sera exécu-tée que par un seul thread à la fois.

public synchronized boolean débiter (int montant) {/* instructions */

}

Comment ça fonctionne ? par la manipulation implicite d’un «verrou d’exclusion mutuelle»…

Java et les moniteursChaque objet est associé à un «moniteur», qui contrôle l’accès au code de l’objet au

1. Time slice, ou alors priorité supérieure du thread « femme »..

ELS - 3 février 2003

Page 140: Programmation Objet Avec Java

POO avec Java - 132 - Objets actifs

moyen d’un «verrou».

Le verrou peut être ouvert ou fermé.

A priori et par défaut, le verrou est ouvert : un nombre indéterminé de threads peuvent accéder simultanément aux méthodes de cet objet.

Quand le verrou est fermé, les sections critiques de cet objet ne peuvent être exécutées que par un seul thread : celui qui a fermé le verrou.

De tels verrous sont aussi désignés par le terme «verrous d’exclusion mutuelle».

Dans ce qui suit, nous appellerons «bloc d’instructions synchronisé» toute section cri-tique, c’est-à-dire tout bloc atomique d’instructions ne pouvant être exécuté que par un seul thread à la fois.

En java, un bloc d’instructions synchronisé s’écrit ainsi :

synchronized (unObjet) {/* instructions */

}

L’objet dont la référence est placée entre parenthèse indique l’objet qu’il faudra ver-rouiller pour pouvoir exécuter les instructions du bloc.

Si toutes les instructions d’une méthode doivent être placées dans une section critique, comme ci-dessous :

public void uneMéthode (..) {synchronized(this) {/* instructions de la méthode */

}}

Il est possible, pour simplifier l’écriture, de « synchroniser » la méthode elle-même, ce qui revient, – pour finir -, exactement au même :

public synchronized void uneMéthode (..) {/* instructions de la méthode */

}

Revenons maintenant sur les notions d’ouverture et de fermeture du verrou.

Fermer le verrouUn thread quelconque peut «acquérir le verrou» (on dira encore «entrer dans le moni-teur»). Le verrou est alors fermé pour tous les autres threads.

Il suffit pour cela que le thread essaye d’exécuter l’un ou l’autre des blocs synchronisés relativement à cet objet.

ELS - 3 février 2003

Page 141: Programmation Objet Avec Java

POO avec Java - 133 - Objets actifs

Les autres threads qui essaient à leur tour d’accéder à l’un ou l’autre de ces blocs d’ins-tructions synchronisés sont alors bloqués et inscrits dans la liste d’attente associée au verrou.

Précisons pour clarifier les choses que seuls les blocs synchronisés sont contrôlés : les méthodes non synchronisées d’un objet peuvent être exécutées simultanément par plu-sieurs threads, même si l’objet a été verrouillé.

Ouvrir le verrouDès que le thread termine le bloc d’instructions synchronisé, le verrou est automatique-ment «relâché» (on dira encore que le thread a «quitté le moniteur»). Si la liste d’attente n’est pas vide (si d’autres threads ont tenté de verrouiller l’objet), un parmi ces threads est choisi arbitrairement pour occuper le moniteur à son tour.

Un «bloc synchronisé»

Récapitulons la notion de bloc synchronisé :

synchronized (unObjet) {/* instructions */

}

Si un thread essaye d’exécuter un tel bloc :

1. Un verrou est posé sur l’objet référencé par «unObjet» jusqu’à ce que les ins-tructions du bloc soient toutes exécutées ;

2. Pendant ce temps, tout thread qui tenterait de verrouiller à nouveau le même ob-jet serait bloqué et inscrit en file d’attente. Notamment ce serait le cas pour:

• tout thread qui tenterait d’exécuter le même bloc d’instructions synchro-nisé,

• tout thread qui tenterait d’exécuter un autre bloc d’instructions synchro-nisé, mais sur le même objet ,

• tout thread qui tenterait d’exécuter une «méthode synchronisée» du même objet.

Une «méthode synchronisée»Récapitulons maintenant la notion de méthode synchronisée :

public synchronized void uneMéthode (..) {/* instructions de la méthode */

}

Si un thread essaye d’exécuter un telle méthode :

1. un verrou est posé sur l’objet auquel est envoyé le message jusqu’à ce que les instructions de la méthode soient toutes exécutées ;

2. pendant ce temps, toute thread qui tenterait de verrouiller à nouveau le même ob-

ELS - 3 février 2003

Page 142: Programmation Objet Avec Java

POO avec Java - 134 - Objets actifs

jet serait bloqué et inscrit en file d’attente. Notamment ce serait le cas pour:• tout thread qui tenterait d’exécuter la même méthode,• tout thread qui tenterait d’exécuter une autre méthode méthode synchro-

nisée du même objet, • tout thread qui tenterait d’exécuter un « bloc synchronisé » sur le même

objet.

Blocs synchronisés ou méthodes synchronisées ?De manière générale, les sections critiques doivent être les plus courtes possibles. On améliore ainsi le temps de réponse du système : pendant qu’un thread occupe une sec-tion critique, les autres threads qui aimeraient y avoir accès sont bloqués en attendant la libération du verrou. C’est ennuyeux. Par ailleurs, plus les sections critiques sont cour-tes, plus les chances d’interblocage sont réduites..

Comme on l’a vu, une méthode synchronisée pourrait être écrite ainsi : public void uneMéthode (..) {

synchronized(this) {/* instructions de la méthode */

}}

Il faut alors se poser la question suivante : est-il vraiment nécessaire de synchroniser toutes les instructions de la méthodes ? A répondre de cas en cas, suivant le contexte.

La synchronisation de méthodes statiques (méthodes de classes)Chaque objet est associé à un verrou. C’est également le cas des classes…

En effet, une classe, d’un certain point de vue, peut être considérée comme un objet. Une classe dispose en effet de ses propres variables et de ses propres méthodes (élé-ments « static »).

C’est ainsi que chaque classe est associée à un verrou qui contrôlera l’exécution de blocs d’instructions synchronisés sur la classe elle-même, en assurant (si le verrou est posé) que seul le thread détenteur du verrou a le droit d’exécuter ces blocs.

Il est donc possible de déclarer qu’une méthode statique est synchronisée :public synchronized static uneMéthodeDeClasse(..) {…}

ou alors de synchroniser un bloc d’instructions sur une classe :synchronized (uneClasse) {

/* instructions */}

ELS - 3 février 2003

Page 143: Programmation Objet Avec Java

POO avec Java - 135 - Objets actifs

comme par exemple :synchronized (Class.forName("NomDuneClasse")) {

/* instructions */}

ou encore de synchroniser un bloc d’instructions sur une classe à partir de l'une de ses variables statiques :

synchronized (variableStatique) {/* instructions */

}

5.7 RENDEZ-VOUS ENTRE THREADSDans le domaine de la programmation concurrente, il ne suffit pas d’éliminer les problè-mes dus au hasard de l’ordonnancement (les « race conditions »). Les threads doivent aussi avoir la possibilité de coopérer.

En effet, si tous les threads de votre application sont capables de s’exécuter de manière tout à fait indépendante les uns des autres, le mécanisme d’exclusion mutuelle peut suf-fire.

Toutefois, dans la plupart des cas, les threads concurrents seront amenés à se commu-niquer des informations ou des événements. Il ne s’agit plus d’une compétition, mais plutôt d’une coopération.

Le mécanisme offert par Java

Définition 32: Attente sur un événement

Un ou plusieurs threads pourront attendre «qu’il se passe quelque chose» sur un objet. C’est un autre thread qui mettra fin à leur attente.

En d’autres termes, un thread pourra se mettre en attente d’un événement. Il attendra alors qu’un autre thread l’informe, le notifie, de l’occurrence de l’événement sur le même objet que celui sur lequel le thread s’est mis en attente.

C’est possible grâce aux méthode wait, notify et notifyAll.

Si un thread «Ta» envoie le message wait() à un objet quelconque «x», le thread «Ta» est bloqué en attendant qu’il se «passe quelque chose» en rapport avec cet objet «x». Le déblocage aura lieu dès qu’un thread «Tb» aura signifié à l’objet «x» «qu’il s’est passé quelque chose» en lui envoyant le message notify(), ou noti-fyAll().

Si plusieurs threads ont envoyé le message wait(), ils seront tous inscrits dans une file d’attente associée à l’objet.

ELS - 3 février 2003

Page 144: Programmation Objet Avec Java

POO avec Java - 136 - Objets actifs

Le message notifyAll() aura pour effet de débloquer tous les threads se trouvant en file d’attendre.

Le message notify()débloque uniquement un thread, choisi arbitrairement dans la liste d’attente.

Voici un exemple connu: le rendez-vous des étudiants avec leur notes…

class ListeNotes {synchronized void seFontAttendre () {

try {wait() ;

}catch (InterruptedException e) {}

}

synchronized void êtreDiffusees () {notify() ;

}}

class Eleve () {

public Eleve (ListeNotes sesNotes) () {..}:sesNotes.seFontAttendre()System.out.println (« J’ai eu mes notes ») ;:

}

class Prof {public Prof (ListeNotes lesNotes) () {..}:lesNotes.etreDiffusees()System.out.println (« VOICI vos notes ») ;:

}// Utilisation des deux classesListeNotes aieAie = new Notes() ;Prof prof = new Prof (aieAie) ;Eleve eleve = new Eleve (aieAie) ;

Remarquons que si jamais élève et professeur ne travaillent pas sur le même paquet de notes, il faudra tuer l’élève (CONTROL C ) pour abréger son attente.

Si la classe comporte plusieurs élèves : il suffit de remplacer notify() par noti-fyAll()

La méthode wait dispose d’un « timeout » possible.

ELS - 3 février 2003

Page 145: Programmation Objet Avec Java

POO avec Java - 137 - Objets actifs

Dans le détail..Regardons maintenant de manière plus formelle la sémantique des méthodes wait(), notify() et notifyAll().

Du point de vue de la synchronisation, remarquons en préliminaire qu’un objet :

1. est associé à un verrou, qui contrôle l’entrée dans le moniteur. Si le moniteur est déjà occupé, tous les threads qui désirent acquérir le verrou sont inscrits dans une liste d’attente : la liste «monitor_wait» ;

2. est associé à un ensemble de threads, placés dans la liste «condvar_wait», de tous les threads qui ont envoyés le message «wait()» à cet objet.[Voir paragraphe - Diagramme d’états et de transitions - page 117]

Sémantique de «wait()» Pour invoquer la méthode «wait()» sur un objet «x», un thread «t» doit d’abord avoir pénétré dans le moniteur de cet objet en ayant acquis son verrou.Comme par exemple :

synchronized (x) {x.wait() ;:

}

1. Le thread est inscrit dans la liste «condvar_wait» de l’objet a qui est envoyé le message.

2. Le thread est bloqué.3. Le verrou acquissur l’objet «x» est libéré. Ce dernier devra être récupéré lorsque

le message notify() ou notifyAll() aura été envoyé, pour que le thread puisse repassé à l’état prêt.

Le réveil du thread «t» aura lieu quand :• un autre thread envoie un notify()à l’objet «x», et que le thread en question

a la chance d’être choisi au sein de la liste «condvar_wait». Ce choix est arbitraire, Java ne précise rien ;

• ou alors, quand un autre thread envoie le message notifyAll() à l’objet «x». Dans ce cas, tous les threads de la liste «condvar_wait » sont réveillés ;

• ou enfin, quand le délai spécifié de manière optionnelle dans le message «wait(msec)» est échu.

Qu’advient-il alors du thread «t» ?

1. ce dernier est enlevé de la liste «condvar_wait » ;2. mis en état de tentative de verrouillage de l’objet «x». Il est mis éventuellement

en compétition avec d’autres.3. la méthode «wait()» est alors terminée, et le thread «t» est introduit dans la

liste des threads à l’état « Prêt ». Notons qu’à cet instant, le thread «t» possède toujours le verrou sur l’objet «x».

ELS - 3 février 2003

Page 146: Programmation Objet Avec Java

POO avec Java - 138 - Objets actifs

Figure 21: Synchronisation: le diagramme d'états et transitions

Sémantique de la méthode «notify()»Comme pour «wait()», cette méthode peut être invoquée par un thread «t» sur un ob-jet «x» à condition seulement que «t» ait acquis le verrou de «x» :

synchronized (x) {x.notify() ;:

}

Début:

Terminé (mort)Construit

En exécution

Actif

start()

schedulingPrêt(exécutable)

Inactif(bloqué)

new Thread(..)

Endormisuspend()

sleep(msec)

Fin Entrée/Sortie

synchronized(y)

Pause terminée

Opération Entrée/Sortie

run() a terminé son exécution(terminaison normale)

stop()

stop()

stop()

Endormi

Endormi

Endormi,dans la liste

"monitor_wait"de l'objet "y"

Endormi,dans la liste

"condvar_wait"de l'objet "x"

resume()

x.wait()

entry: libérationdu verrou sur x

x.notify(),x.notifyAll()

doit acquérir à nouveaule verrou sur x

Acquisitiondu verrou

x.wait(msec)

ELS - 3 février 2003

Page 147: Programmation Objet Avec Java

POO avec Java - 139 - Objets actifs

Un des threads de la liste «condvar_wait » est alors choisi au hasard afin d’être réactivé. Si cette liste est vide, la méthode «notify()» n’a aucun effet.

Le thread «t» continue son exécution et continue notamment d’occuper le moniteur de «x». Ainsi, le thread qui a été réactivé ne continuera son exécution qu’une fois que le thread «t» aura quitté le moniteur.

Sémantique de la méthode « notifyAll()»Fonctionnement identique à «notify()», sinon que la liste «condvar_wait » est vidée complètement.

5.7.1 Un modèle standard pour utiliser «wait()»Il y a un modèle standard qu’il est recommandé d’utiliser avec «wait» et «notify». Un thread qui attend une condition pour faire quelque chose devrait appeler une méthode «faireSi-Condition()» qui devrait être écrite un peu comme ceci :

class X extends Thread {

boolean condition; // Condition à attendre

public void run () {:faireSiCondition ();:

}

public synchronized void faireSiCondition () {

while (!condition) {try {

wait();..Pendant le «wait», le moniteur est libéré.. A cet endroit de l'exécution (après le«wait»), le thread est entré à nouveau dansle moniteur, mais, entre temps, la condition n'est peut-être plus vraie. La boucle "while" est donc nécessaire, ne pas la remplacer par un «if» !!

}catch (InterruptedException e) {}

}}

public void signaler () {// Méthode appelée par un autre thread pour signaler que// la condition est arrivée

synchronized (this) {condition = true;notify();

}}::

}

ELS - 3 février 2003

Page 148: Programmation Objet Avec Java

POO avec Java - 140 - Objets actifs

Au travers de cet exemple, on peut voir pourquoi les méthodes wait() et notify()doivent être utilisées en conjonction avec le verrou de synchronisation. Si ça n’était pas le cas, le risque de « race condition » serait important.

C’est une fois seulement le thread bloqué, que le verrou est libéré et qu’un notify()peut arriver.

Si la libération du verrou était effectuée avant le blocage, le thread risquerait d’être notifié avant même d’être bloqué. Auquel cas la notification serait perdue…

Figure 22: Perte d'un notification

5.7.2 Exemple: une file d'attenteVoici à titre d’illustration la réalisation d'une file d'attente susceptible d'être partagée en-tre plusieurs threads en lecture ou en écriture. Tout thread qui tentera d'extraire un élé-ment de la queue, alors que cette dernière est vide, sera mis en attente.

thread 1 thread 2

oui

préemption

set condition

notify()

condition OK ?

wait()

attente perpétuelle: le "notify" ne viendra plus

notification ignorée

ELS - 3 février 2003

Page 149: Programmation Objet Avec Java

POO avec Java - 141 - Objets actifs

class Fifo {

Element tête, queue ;

public synchronized void ajouter (Element e) {if (queue == null) // La queue était vide

tête = p ; // Alors, inscrire en tête

else // Sinon, inscrire en queue

queue.prochain = p ;

p.prochain = null ;queue = p ;notify() ; // un thread éventuel en attente

// sera notifié}

public synchronized Element get () {// Extraire l’élément en tête de queue

while (tête == null) {// La queue est vide, attendretry {wait() ;}catch (InterruptedException e)

{return null ;}}Element p = tête ;tête = tête.prochain ;if (tête == null) queue = null ;

// La queue est vide maintenant

return p ;}

}

5.8 LES VARIABLES «VOLATILE»«volatile» est un mot-clé que le programmeur utilisera pour marquer certaines varia-bles d’instance «sensibles», comme par exemple:

private volatile uneVariable;

Pourquoi devrait-on «volatiliser» une variable?

Un tel marquage a pour but de désactiver certaines optimisations opérées par le sys-tème d’exécution et le compilateur. Notamment, ces derniers ont le droit de considérer que deux références à la même variable, opérées au sein d’une même méthode, alors même que cette variable n’y subit aucune affectation, retourneront la même valeur..

Sur cette base, le système peut décider qu’il suffira d’accéder une seule fois à la varia-ble, en copiant sa valeur dans un registre interne qui sera par la suite plus rapide d’accès.

Si cette pratique peut paraître judicieuse quand elle s’applique à des méthodes synchro-nisées ou encore à des méthodes exécutées par un thread unique, il est souvent néces-saire de désactiver cette optimisation.

ELS - 3 février 2003

Page 150: Programmation Objet Avec Java

POO avec Java - 142 - Objets actifs

Typiquement, une méthode dont l’activité principale consiste en une boucle qui attend le changement d’état d’une variable, comme par exemple:

public void run() {while (activite == Thread.currentThread()) {

try {thisThread.sleep(interval);

}catch (InterruptedException e){}repaint();

}}

Pour être certain que cette fonction run() fonctionne correctement dans tous les cas de figure, le programmeur a tout intérêt à déclarer la variable activite comme une var-iable volatile:

private volatile Thread activite;

5.9 LES THREADS DÉMONS

Ce sont typiquement des threads de faible priorité qui tournent en boucle infinie, en attendant qu’on leur demande de rendre un service : nettoyer la mémoire, afficher des images, etc...

1. la méthode setDaemon() permet de spécifier qu’un thread est un démon. Elle doit être utilisée avant que le thread ne soit démarré (start()) ;

2. La méthode isDaemon() permet de savoir si un thread est un démon..

Quelle est la différence entre un «thread démon» et un «thread normal» (encore appelé «thread utilisateur») ?

Aucune, sinon qu’au niveau système, la machine virtuelle Java n’attendra pas la mort d’un thread démon pour arrêter le programme : Java décidera de tuer lui-même les threads démons quand il ne restera plus qu’eux en jeu.

Les threads utilisateurs, tant qu’il en reste au moins un, empêchent la machine virtuelle d’arrêter le programme. Le programme se termine tantqu’ils ne sont pas morts par sui-cide ou par assassinat.

Le garbage collector est un exemple typique de «thread démon».

5.10 LES GROUPES DE THREADSIl s’agit d’objets de la classe «ThreadGroup».

Dans tout programme java, il existe au moins un groupe : le groupe « main ».

ELS - 3 février 2003

Page 151: Programmation Objet Avec Java

POO avec Java - 143 - Objets actifs

A la création d’un thread, il est possible de spécifier le groupe auquel il appartient :

Thread unThread =new Thread ( stringDuNomDuGroupe,

stringDuNomDuThread) ;

Par défaut, un thread appartient au même groupe que son père (thread créateur),

Par défaut, donc, tous les thread créés appartiennent au groupe « main ».

Quand on crèe un groupe, ce dernier est le fils du groupe courant... on crèe ainsi une hiérarchie de groupes... c’est important car certaines actions effectuées au niveau d’un groupe sont parfois implicitement effectuées sur les enfants.

Les méthodes suivantes ne modifient pas les threads du groupe déjà existants:

• setDaemon()

• setMaxPriority()

Les méthodes suivantes agissent sur tous les threads du groupe et des groupes descen-dants:

• resume()

• stop()

• suspend()

Les méthodes suivantes permettent de connaître les attributs du groupe• getDaemon()

• getMaxPriority()

• getName()

• getParent()

Intérêt des groupes : pouvoir appliquer une action à tout un ensemble de threads sans avoir besoin de conserver une référence sur chacun d’entre eux.

5.11 LES MÉTHODES «STOP()», «SUSPEND()» ET «RESUME()» SONT DÉPRÉCIÉES DANS JAVA 1.2

Ces deux méthodes ont toujours été sujettes à controverses. La version 1.2 de Java les élimine..

Une bonne raison de se méfier de ces deux méthodes à trait au risque d’interblocage qu’elles suscitent.

Considérons à titre d’exemple le bout de code suivant :

ELS - 3 février 2003

Page 152: Programmation Objet Avec Java

POO avec Java - 144 - Objets actifs

class uneClasse {//...

synchronized void f() {Thread.currentThread().stop() ;

}}

Un thread qui désire exécuter la méthode «f()» doit d’abord verrouiller l’objet. Au moment où le thread est stoppé, il occupe le moniteur. Or ce dernier n’est pas libéré par la méthode stop() !

C’est comme si quelqu’un entrait dans la salle de bains, fermait la porte et sortait par la fenêtre : tout personne qui attendrait la libération de la salle de bains serait bloquée à jamais !

Le problème est identique avec suspend() (le déblocage du verrou n’aura pas lieu tant que le thread n’aura pas été réactivé par un resume()).

A ce titre, notons que la méthode sleep(..) est également dangereuse (c’est comme si une personne s’endormait dans la salle de bains).

5.11.1 Les dangers du message stop(), comment le remplacerLa réception du message stop() a pour effet de libérer tous les moniteurs que le thread avait verrouillé. Ainsi, les objets qui étaient au préalable protégés par ces moniteurs de-viennent visibles aux autres threads, et le comportement qui en résultera peut avoir des conséquences imprévisibles et dangereuses.

L’envoi du message stop() devrait être remplacé par des instructions modifiant l’état d’une variable, qui, contrôlée de manière continuelle par le thread, indiquerait à ce der-nier qu’il doit terminer son exécution. Le thread condamné à mourir peut alors opérer la libération des moniteurs qu’il a verrouillé selon une procédure qui ne présente pas de danger.

Au cas où le thread en question est en attente pendant une période de longue durée, il est possible de l’interrompre au moyen du message interrupt().

L’exemple suivant , - proposé par Sun -, préconise de remplacer l’assassinat du thread (effet normal d’un stop()) par une incitation au suicide. Avant d’accomplir le geste fatidique, le thread a l’occasion de libérer toutes les ressources qu’il possède: «vous êtes viré, vous avez deux secondes pour libérer les lieux».

ELS - 3 février 2003

Page 153: Programmation Objet Avec Java

POO avec Java - 145 - Objets actifs

class Xxx implements Runnable {

private volatile Thread blinker;// La raison d’être du mot-clé «volatile» est expliquée

dans le paragraphe consacré à ce mot-clé [Voir paragraphe - Les variables «volatile» - page 141]

public void start() {blinker = new Thread(this);blinker.start();

}

public void stop() {blinker = null;

}

public void run() {Thread thisThread = Thread.currentThread();while (blinker == thisThread) {

try {thisThread.sleep(interval);

} catch (InterruptedException e){}

repaint();}

}

public void paint (Graphics g) {/* dessin du clignotant */

}}

5.11.2 Les dangers du message suspend(), comment le remplacerLa méthode suspend() a été dépréciée en raison des riques potentiels d’interblocage que son usage peut entraîner. En effet, si le thread a verrouillé un moniteur dédié à la protection d’une ressource critique, ce moniteur reste verrouillé pendant toute la durée de la suspension, empêchant ainsi tout accès à la ressource critique, jusqu’à ce que le thread soit réveillé (resume()). Si jamais le thread sensé envoyer le message resu-me() essaye de poser un verrou sur le moniteur en question avant d’avoir envoyé le mes-sage resume(), l’interblocage en résulte immédiatement.

Voici une bout de programme dangereux..

private boolean threadSuspended;

public void mousePressed(MouseEvent e) {e.consume();if (threadSuspended)

blinker.resume();else

blinker.suspend(); // ATTENTION DEAD-LOCK!threadSuspended = !threadSuspended;

}

ELS - 3 février 2003

Page 154: Programmation Objet Avec Java

POO avec Java - 146 - Objets actifs

Voici une solution de remplacement qui permettra de contrôler les événements de manière plus sûre: plutôt que d’envoyer le message suspend(), on demandera au thread de se mettre en attente par le biais d’une variable (dans notre exemple, la varia-ble threadSuspended). Le réveil du thread (resume()) sera remplacé par l’envoi d’un message notify(). Ce dernier peut être envoyé par plusieurs différents threads, en non pas uniquement le thread à l’origine de la suspension.

On remplacera le gestionnaire d’événement par: public synchronized void mousePressed(MouseEvent e) {

e.consume();threadSuspended = !threadSuspended;if (!threadSuspended){// Le thread était suspendu

notify();}

Puis on ajoutera le bout de code suivant à la boucle loop de la méthode run() du thread:

synchronized(this) {while (threadSuspended) wait();

}

Ce qui donnerait par exemple ceci:public void run() {

while (true) {try {

Thread.currentThread().sleep(interval);

synchronized(this) {while (threadSuspended)wait();

}} catch (InterruptedException e){}repaint();

}}

Ou mieux, pour éviter les synchonisations inutiles et coûteuses en temps:if (threadSuspended){

synchronized(this) {while (threadSuspended) wait();

}}

Attention ! Dans ce dernier cas, la variable threadSuspended est utilisée endehors d’un bloc synchronisé. Il est conseillé de la marquer comme une variable volatile:

private volatile boolean threadSuspended;

Pourquoi ? [Voir paragraphe - Les variables «volatile» - page 141]

ELS - 3 février 2003

Page 155: Programmation Objet Avec Java

POO avec Java - 147 - Objets actifs

5.12 ANNEXESNous trouverons dans cette annexe une liste exhaustive des méthodes à utiliser dans le cadre de la programmation concurrente en Java.

5.12.1 Méthodes de la classe «Thread»

start()public native synchronized void start()

Lance la méthode «run()» de l’objet passé en paramètre du constructeur du thread.

Throws: IllegalThreadStateException

Si le thread était déjà démarré.

stop()

Méthode dépréciée dès la version 1.2 de Javapublic final void stop()

Pour tuer le thread (l’objet continue d’exister cependant jusqu’à ce qu’il ne soit plus référencé).Il est fortement recommandé de ne pas utiliser cette méthode pour terminer les thread. La seule méthode reconnue consiste à laisser le thread terminer norma-lement son exécution après avoir exécuté la dernière instruction de la méthode run()associée.L'utilisation de la méthode stop témoigne surtout d'un manque de réflexion de la part du programmeur…[Voir paragraphe - Les méthodes «stop()», «suspend()» et «resume()» sont dépréciées dans java 1.2 - page 143]

sleep(...)

Pour endormir le thread, méthodes de classe.

public static native void sleep(long millis)throws InterruptedException

public static void sleep(long millis, int nanos)throws InterruptedException

Le thread qui envoie l'un ou l'autre de ces messages est endormi, le temps d'une pause exprimée en millisecondes (plus éventuellement quelques nanosecon-des).Pendant cette pause, le thread conserve le moniteur dans il aurait le contrôle.

ELS - 3 février 2003

Page 156: Programmation Objet Avec Java

POO avec Java - 148 - Objets actifs

Parameters:millis – durée de la pause en millisecondes.

nanos - 0-999999 nanosecondes additionnelles de pause.

Throws: IllegalArgumentException

Si la valeur de millis est negative ou si la valeur de nanos n'appartient pas à l'intervalle 0-999999.

Throws: InterruptedException

Si le thread a été interrompu par un autre thread

suspend ()

Méthode dépréciée dès la version 1.2 de Javapublic final void suspend()

Pour endormir le thread jusqu’à ce qu’un autre thread lui envoie le messageresume().

Le thread continue de posséder tous les verrous qu’il a acquis auparavent.Si le thread est dans l'état Actif, son exécution est suspendue jusqu'à ce qu'il reçoive le message «resume()».En premier lieu, la méthode «checkAccess» de cette méthode est appelée pour vérifier que le thread courant a la possibilité d'accéder à ce thread. Il peut en résulter la levée de l'exception «SecurityException»

Throws: SecurityException -

Non encore implémenté dans Java !

Si le thread courant ne peut pas modifier ce thread[Voir paragraphe - Les méthodes «stop()», «suspend()» et «resume()» sont dépréciées dans java 1.2 - page 143]

resume()

Méthode dépréciée dès la version 1.2 de Java

public final void resume()

Pour réveiller un thread qui aurait été endormi avec le message «suspend()».En premier lieu, la méthode checkAccess de cette méthode est appelée pour vérifier que le thread courant a la possibilité d'accéder à ce thread. Il peut en résulter la levée de l'exception «SecurityException»

Throws: SecurityException -Non encore implémenté dans Java !Si le thread courant ne peut pas modifier ce thread[Voir paragraphe - Les méthodes «stop()», «suspend()» et «resume()» sont dépréciées dans java 1.2 - page 143]

ELS - 3 février 2003

Page 157: Programmation Objet Avec Java

POO avec Java - 149 - Objets actifs

isAlive()

public final native boolean isAlive()

Retourne true dans les états Actif ou Inactif, retourne false dans les autres cas (états construit ou mort).

yield()

public static native void yield()

Rendre le processeur pour le donner à un thread de même priorité (passe à l'état Prêt)

interrupt()

public void interrupt()

Pour interrompre le thread.En premier lieu, la méthode checkAccess est invoquée, et peut entraîner la levée de l’exception SecurityException.Si ce thread est bloqué suite à l’invocation de wait(..), ou encore par l’invoca-tion de join(..), son état «interrupt» est marqué comme «false», et le thread recevra une exception InterruptedException.Si ce thread est bloqué suite à une requête d’entrée/sortie opérée sur un canal interruptible, le canal sera alors fermé, l’état «interrupt» du thread est marqué comme «true», et le thread recevra une exception ClosedByInterrupte-dException.Voir aussi dans la documentation le cas où le thread est bloqué dans un «sélec-tor».Dans tous les autres cas de figure, l’état «interrupt» du thread est simplement marqué comme «true».

Throws:

SecurityException - si le thread courant n’a pas le droit de modifier ce thread

interrupted()

public static boolean interrupted()

Pour tester si le thread courant a été interrompu. L’état «interrupted» du thread est marqué comme «false» par l’nvocation de cette méthode. Ainsi, si cette méthode était invoquée deux fois de suite, le deuxième apple retournerait false (à moins bien sûr que le thread ait été à nouveau inerrompu entre temps).

Returns:

true si le thread a été interrompu, false dans les autres cas

ELS - 3 février 2003

Page 158: Programmation Objet Avec Java

POO avec Java - 150 - Objets actifs

isInterrupted()

public boolean isInterrupted()

Pour tester si ce thread a été interrompu. L’état «interrupted» du thread n’est pas affecté par l’nvocation de cette méthode.

Returns:

true si le thread a été interrompu, false dans les autres cas.

setDaemon()

public final void setDaemon(boolean on)

La méthode setDeamon() permet de spécifier qu’un thread est un démon, ou un thread « normal ». Cette méthode doit être appelée avant que le thread ne soit lancé (start()).

Throws: IllegalThreadStateException

si le thread est actif

isDaemon()

public final boolean isDaemon()

La méthode isDeamon() permet de savoir si un thread est un démon.

join()

public final void join() throws InterruptedException

Pour attendre la mort d’un thread.

Throws: InterruptedException

Si le thread courant est interrompu par un autre thread.

5.12.2 Méthodes de la classe «Object»

wait(..)

1. public final void wait()throws InterruptedException

2. public final void wait(long timeout)

throws InterruptedException

Le timeout spécifié en msec

3. public final void wait(long timeout, int nanos)throws InterruptedException

Méthode identique à la précédente, avec la possibilité de spécifier des

ELS - 3 février 2003

Page 159: Programmation Objet Avec Java

POO avec Java - 151 - Objets actifs

nanosecondes additionnelles.

Parameters:

timeout- pour spécifier une durée maximum de l’attente.

Pour se bloquer en attendant d’être notifié par un autre thread.Au préalable, le thread courant doit avoir verrouillé l’objet.Le thread libère le moniteur et attend d’être notifié par un autre thread (au tra-vers des messages notify() ou notifyAll()). Le thread attend d’obtenir à nouveau le moniteur qu’il occupait avant le wait(), puis recommence son exécution.

Throws IllegalMonitorStateException

Si le thread courant n’a pas verrouillé l’objet.

Throws InterruptedException

Un autre thread a interrompu le thread courant

notify()

public final native void notify()

Pour réveiller un thread qui s’est mis en attente sur même objet (au travers d’un wait()).Pour invoquer cette méthode, il est obligatoire d’avoir verrouillé l’objet (au tra-vers d’une synchronisation).Si plusieurs threads sont en attente de notification, un seul thread est choisi pour être réactivé. Le choix est opéré de manière arbitraire.

Throws: IllegalMonitorStateException Si le thread courant n’a pas verrouillé l’objet.

notifyAll()

public final native void notifyAll()

Pour réveiller tous les threads qui se sont mis en attente sur même objet (au tra-vers d’un wait()). Les threads sont réveillés dans un ordre tout à fait arbi-traire.Pour le reste, cette méthode est identique à notify().

ELS - 3 février 2003

Page 160: Programmation Objet Avec Java

POO avec Java - 152 - Objets actifs

ELS - 3 février 2003

Page 161: Programmation Objet Avec Java

POO avec Java - 153 - Etude de cas Une application Client-Serveur

6 Etude de cas Une application Client-Serveur

Nous étudierons dans ce document un exemple d'application Client-Serveur avec ser-veur concurrent. La communication inter-processus sera basée sur le mécanisme des

ELS - 3 février 2003

Page 162: Programmation Objet Avec Java

POO avec Java - 154 - Etude de cas Une application Client-Serveur

sockets. L'exemple proposé est une discussion sur internet permettant à plusieurs parti-cipants de communiquer entre eux par le biais de messages.

Dès qu'un participant (un «client») envoie un message au «serveur», ce dernier le ren-voie à tous les participants à la discussion.

Nous commencerons par présenter le mécanisme des sockets, conçu et développé histo-riquement pour les systèmes UNIX. Puis nous présenterons la mise en œuvre des soc-kets sous Java, et nous nous attarderons enfin sur l'exemple de l'application Client-serveur, en guise d'illustration.

6.1 ENTRÉES/SORTIES SOUS UNIX: LES SOCKETSNotons en préliminaire que la gestion des périphériques d'entrées/sorties sous UNIX, - tels que terminaux, disques, imprimantes, et communications -, utilise une approche qui consiste à intégrer cette gestion dans le système des fichiers.

Dans cette optique, les périphériques sont considérés comme des fichiers spéciaux.

Par exemple, l'imprimante sera assimilée au fichier spécial «lp». Ce fichier est affecté à un chemin, situé très souvent dans le catalogue /dev.

On trouvera ainsi:

/dev/lp pour l’imprimante/dev/tty pour le terminal/dev/net pour le réseau

6.1.1 Les fichiers spéciauxLes fichiers spéciaux sont accessibles de la même manière que les autres fichiers. Du point de vue de l'utilisation, les commandes «Open», «Close», «Read» et «Write» s'appliquent aux fichiers spéciaux de la même manière qu'avec les autres fichiers.

Client

Client

Client

Serveur

Réseau

Le serveur maintient une liste de connexions avec des clients.Cette liste est mise à jour dès qu'un nouveau client se connecte (ou se déconnecte)Les textes renvoyés sont identifiés par le sigle du client

ELS - 3 février 2003

Page 163: Programmation Objet Avec Java

POO avec Java - 155 - Etude de cas Une application Client-Serveur

Premier avantage Ce concept permet de manipuler les périphériques comme des fichiers tout à fait ordinaire et il n'y a donc pas besoin de commandes spéciales.Par exemple, pour imprimer un fichier, il suffit de «copier» le fichier en ques-tion sur sur le fichier «lp»:

> cp fichier /dev/lp

Deuxième avantageIl est possible d'appliquer les règles de protection des fichiers à la protection des périphériques.Par exemple, pour interdire l'accès de l'imprimante aux programmes utilisateur, il suffit d'autoriser le fichier en écriture uniquement à l'utilisateur «système».

Troisième avantageLes fichiers spéciaux, qui disposent de commandes «standard» pour l'accès en écriture ou en lecture, assurent une certaine indépendance des programmes vis à vis du matériel.

6.1.2 Les «sockets»Au départ, les sockets ont été prévues pour mettre en oeuvre la communication entre ter-minaux.

Le mécanisme est apparu dans le système UNIX de Berkeley.

Aujourd'hui, le mécanisme des sockets est utilisé principalement pour permettre à deux processus situés sur des machines distantes de communiquer au travers du réseau.

A l'instar des périphériques d'entrées/sorties, les sockets sont manipulées comme des fichiers.

En particulier, les «sockets» (ou «prises») fonctionnent de la manière suivante:

1. Les sockets peuvent être créées ou détruites dynamiquement ;2. Lors d'une création, un descripteur de fichier est retourné. Ce même descripteur

sera utilisé ensuite pour créer la connexion (open) , lire et écrire des données (write, read), puis enfin pour libérer la connexion (close).

Il existe différents types de sockets, qui assurent chacun un protocole de communica-tion particulier. Notamment, on peut citer les 2 types les plus courants:

communication fiable, orientée connexion,communication de paquets non fiable

Communication fiableCe type de communication permet d'établir l'équivalent d'un «tube» («pipe» ) entre deux

ELS - 3 février 2003

Page 164: Programmation Objet Avec Java

POO avec Java - 156 - Etude de cas Une application Client-Serveur

processus distants.

Il s'agit d'une communication de type «Stream» («flot»): le tube est un «pseudo-fichier», le processus émetteur écrit dans le tube comme s'il écrivait dans un fichier. Le récepteur lit les données du tube comme s'il lisait dans un fichier. Les octets qui sont émis à une extrémité sont reçus dans l'ordre à l'autre extrémité.

Tout byte, émis par le processus émetteur au moyen d'un appel à la primitive «write», ne sera envoyé que lorsque le processus récepteur aura fait appel à un «read» corres-pondant.

En principe, les deux opérations «write» et «read» sont bloquantes, à moins que le tube ne dispose d'un tampon, ce qui permettrait de libérer partiellement le processus émetteur.

Deux modes de communication sont prévus: le mode «octet» et le mode «paquet».

1. Dans le mode «octet» les octets sont reçus par le récepteur en respectant l'ordre avec lequel ils ont été envoyés, mais sans tenir compte du fait qu'ils soient en-voyés byte par byte ou paquet par paquet.Par exemple, supposons que l'émetteur dépose 2560 bytes au moyen de 5 appels à «write» de 512 bytes chacun. Si le récepteur demande 2560 bytes au moyen d'un seul appel à «read», les 2560 bytes seront envoyés en une seule fois.

2. Dans le mode «paquet», les données sont envoyées en respectant la frontière des paquets définis par l'émetteur.Dans l'exemple précédent, le récepteur devra faire appel à 5 «read» successifs d'au moins 512 bytes chacun pour que les 2560 bytes soient envoyés. Ils seront envoyés en 5 fois, paquet par paquet.

Communication non fiable de paquetsAvec ce dernier type de communication, si plusieurs paquets sont envoyés par l'émet-teur, le protocole ne garantit pas qu'ils seront reçus correctement par le receveur. D'autre part, si plusieurs paquets sont envoyés, le protocole ne garantit pas non plus qu'ils seront reçus dans le même ordre.

Processusemetteur

socket socket

réseau (vu comme un tube)write(bytes)

Processusrécepteur

read(bytes)

ELS - 3 février 2003

Page 165: Programmation Objet Avec Java

POO avec Java - 157 - Etude de cas Une application Client-Serveur

Ce type de communication, efficace mais de bas niveau, offre un accès brut au réseau. Il est utilisé notamment dans les applications temps réel qui mettront en oeuvre un mécanisme de gestion d'erreurs spécifique.

6.2 JAVA, LE MODÈLE CLIENT-SERVEUR ET LES SOCKETSEn préliminaire, nous dirons quelques mots à propos du modèle Client-Serveur auquel nous allons souscrire dans le cadre de cette application.

Le modèle «Client-Serveur»Le modèle Client-serveur s'appuie sur la présence d'un serveur et d'un client. Le serveur propose des services, et le client utilise les services offerts par le serveur.

En principe, le serveur attend en permanence qu'un nouveau client se connecte. Dès qu'un client réclame une connexion, le serveur est libre de l'accepter ou non. Si oui, il tâchera de satisfaire à la requête du client.

Définition 33: Applications distribuées

Un application Client-Serveur est une application distribuée: ses différentes compo-santes sont généralement situées et exécutées sur des machines distantes les unes des autres et qui doivent communiquer au travers du réseau.

Le modèle Client-serveur peut être réalisé en utilisant le mécanisme des sockets. Tou-tefois, cette façon d'opérer est quelque peu primitive, d'autres services pourraient utili-sés comme les «RMI» (invocation de méthodes à distance) ou CORBA.

Si le mécanisme des sockets est utilisé, il est impératif que le «client» connaisse l'adresse physique du «serveur». Si d'aventure le serveur était déplacé, il serait néces-saire de mettre à jour l'application client. Ca n'est pas le cas des applications basées sur CORBA: l'outil de déploiement maintient un annuaire de noms logiques, et génère automatiquement les adresses physiques, de manière transparente pour le program-meur.

Définition 34: Le modèle «3-tiers»

Un modèle Client-serveur 3-tiers (en 3 parties) décompose l'application en 3 parties distinctes:

1er tier: la partie «Client»Partie responsable de la «présentation», c'est-à-dire de l'interface avec l'utilisa-teur. Cette partie effectue peu de traitements et se contente généralement de contrôler à un premier niveau les informations saisies par l'utilisateur. En éliminant la fonction «traitement» du côté Client, on tâche de limiter les communications avec la base de données, et ainsi de limiter la charge réseau. Dans le modèle 2-tiers, le client est directement en contact avec la base de don-

ELS - 3 février 2003

Page 166: Programmation Objet Avec Java

POO avec Java - 158 - Etude de cas Une application Client-Serveur

nées.Par ailleurs, ce modèle implique un découplage assez net entre la présentation (la VUE) et les traitements (MODELE-CONTROLEUR). Ce faisant, on se rapproche du modèle de conception MVC (MODELE-VUE-CONTROLEUR) qui préconise justement ce genre de découpage.[Voir paragraphe - Le modèle MVC : «Modele-Vue-Contrôleur» - page 98]En général, la partie client est réalisée par une applet (ce que l'on appelle un «client léger») ou encore en HTML dynamique (clients «hyper-légers»).

2ème tier: la partie «Serveur»Cette partie est responsable des traitements: elle est en général en relation directe avec la base de données dans laquelle sont enregistrées les informations persistantes de l'application. Si la base de données est située sur la même machine, ce qui est le cas en général, le réseau n'est sollicité que pour permettre les échanges entre le client et le serveur.En général, la partie serveur sera réalisée au moyen d'une application. En Java, il pourra s'agir d'une application autonome, d'une servlet (pour la génération de pages HTML dynamique), ou encore de composants EJB (Entreprise Java Beans).

3ème tier: le gestionnaire de base de donnéesCette dernière partie est responsable du stockage des informations persistan-tes1.

Parmi les gestionnaires les plus couramment utilisés, on peut citer Oracle, MicrosoftSQLServer, mySQL, Microsoft Access,..

Le modèle Client-Serveur de l'application «chat»Il s'agit d'une petite application bien sûr, mais elle sera tout de même bâtie sur le modèle Client-Serveur 3-tiers, tout étant que le 3ème tier sera absent... cette application, en ef-fet, ne possède aucune information persistante nécessitant un stockage dans une base de données.

Toutefois l'intérêt de cette application n'est pas moindre puisqu'elle constitue un «sque-lette» d'application 3-tier qui peut être réutilisé.

6.2.1 Liaisons UDP et connexions TCP-IPDans l'implémentation Java, le mécanisme des sockets permet au programmeur de choi-sir entre une liaison TCP ou une liaison UDP.

Protocole TCPDan sle cas du protocole TCP, la communication entre les deux partenaires est une com-munication fiable.

1. Les informations persistantes sont les informations dont la valeur doit être conservée entre deux exécutions de l'application

ELS - 3 février 2003

Page 167: Programmation Objet Avec Java

POO avec Java - 159 - Etude de cas Une application Client-Serveur

La liaison, une fois établie entre le client et le serveur, aura lieu de bout en bout, tou-jours avec le même chemin. La liaison est dite «orientée connexion», et s'apparente à une connexion téléphonique.

Le protocole garantit que tout envoi d'informations sera transmis correctement. En effet, chaque envoi est quittancé. En cas de problème, le paquet d'informations est renvoyé.

Enfin, si plusieurs paquets sont envoyés, le protocole garantit qu'ils seront reçus dans le même ordre.

Protocole UDP (datagrammes)La communication entre les deux partenaires est une communication non fiable par pa-quets. Chaque paquet, que l'on appelle datagramme sera délivré sans qu'aucune con-nexion ne soit établie de prime abord. Des envois successifs de paquets seront opérés indépendamment les uns des autres, et emprunteront éventuellement des chemins diffé-rents. Ainsi, ils n'arriveront pas forcément dans l'ordre où ils ont été envoyés. Par ailleurs, il n'est pas garanti qu'un paquet soit transmis correctement (l'envoyeur n'attend pas de quittance, contrairement à TCP).

Si une connexion TCP s'apparente à une connexion téléphonique, une liaison UDP s'apparente plutôt à un envoi de lettres par la poste.

Ce protocole est simple à utiliser: le client comme le serveur doivent créer chacun de leur côté une socket de type «DatagramSocket». Les méthodes «send» et «receive» seront alors utilisées pour envoyer et recevoir des paquets de type «Data-gramPacket».

Ce type de communication, de bas niveau, est très efficace. Il peut être utilisé dans les applications temps réel. En contre partie, ces mêmes applications devront mettre en

Couche physique

ApplicationClient

Couche Réseau

Couche TransportTCPTCP: Transmission Control

ProtocolUDP: User Datagram Protocol

IP: Internet Protocol IP

UDP

Couche Application

Socket Socket

p.e.: EthernetCouches "liaison"

et "physique"

ApplicationServeur

TCP

IP

UDP

Socket Socket

p.e.: Ethernet

Internet Internet

ELS - 3 février 2003

Page 168: Programmation Objet Avec Java

POO avec Java - 160 - Etude de cas Une application Client-Serveur

oeuvre un mécanisme de gestion d'erreurs spécifique. Le protocole UDP est très fré-quemment utilisé pour transmettre des images. Notons que le protocole de gestion de réseau SNMP (Simple Network Management Protocol) s'appuie sur UDP.

Dans la suite du document, nous ne décrirons que le modèle TCP.

6.2.2 Principe d'utilisation des sockets TCPNous décrivons ici l'utilisation de sockets dans l'optique d'effectuer des communications «fiables» basées sur le protocole TCP.

Le serveur sera caractérisé par deux éléments:

1. Une adresse internet (adresse IP), désignant la machine sur laquelle s'exécute l'application serveur,

2. Un numéro de port, qui désigne le numéro du service offert par le serveur. En effet, il est possible que différents services soient mis à disposition par différents sous la même adresse internet, ou encore que le même serveur offre plusieurs services associés à des numéros différents.

Du côté «Client»Le client demandera un service en créant une socket initialisée à partir de l'adresse in-ternet du serveur et du numéro du service requis.

Si on assimile la socket à un appareil téléphonique, la création d'une socket revient à installer un appareil téléphonique, et à composer le numéro du serveur.

try {socket = new Socket (adresseIP, NO_PORT);

}catch (IOException e) { /* "!! PROBLEME DE CONNEXION ! ! */ }

Si le serveur (au bout du fil) décroche son combiné, la communication pourra commen-cer…

Ainsi, pour que la création de la socket soit effective, la connexion correspondante doit être acceptée par le serveur. Dans le cas contraire, une exception de type «IOExcep-tion» est aussitôt levée. Aussi, tournons-nous du côté serveur..

Du côté «Serveur»Du côté serveur, une connexion devra être ouverte en permanence. Cette connexion sera explicitement rattachée au service proposé (no de port), et permettra au serveur de se mettre «à l'écoute» et d'attendre des requêtes de la part de clients.

ELS - 3 février 2003

Page 169: Programmation Objet Avec Java

POO avec Java - 161 - Etude de cas Une application Client-Serveur

A cette fin une socket «d'écoute» doit être installée en créant une socket de type «Ser-verSocket»:

try {socketEcouteur = new ServerSocket (NO_PORT);

}catch (IOException e) { /* "!! PROBLEME DE CONNEXION ! ! */ }

Un «ServerSocket» peut admettre jusqu'à 50 demandes de connexions simultanées (il possède en effet une queue de 50 entrées). Si une connexion est demandée alors que la queue est pleine, cette dernière sera refusée.

Le serveur se met «à l'écoute» de cette socket en lui envoyant le message «accept()»:

Socket nouvelleSocket = socketEcouteur.accept();

La méthode «accept()» est bloquante: le serveur «à l'écoute» est un thread à l'état bloqué qui sera réveillé dès qu'un client effectuera une demande de connexion.

Pour reprendre l'image de la connexion téléphonique, le serveur doit mettre en place un appareil téléphonique, installé en permanence, et attendre que ce téléphone sonne. Si le téléphone sonne et qu'il n'a pas d'autre chose à faire, il décrochera pour commencer la communication.

A la différence d'une communication téléphonique habituelle, le serveur agit plutôt comme un central téléphonique: dès que la communication est acceptée, un nouvel appareil téléphonique est installé provisoirement et la communication est déviée sur ce nouvel appareil. Ainsi, le serveur peut se remettre à l'écoute du premier appareil et attendre une nouvelle sonnerie.

En Java, dès qu'une demande de connexion est opérée par un client, la méthode «accept()» se termine en retournant une nouvelle socket, connectée directement avec le client de bout en bout pour permettre au serveur de communiquer avec ce dernier.

Construire l'adresse IPLe client qui crée une socket doit mentionner l'adresse IP du serveur (voir plus haut).

En Java, les adresses IP sont matérialisées par des objets de type «InetAdress». La création de tels objets passe le plus généralement par l'utilisation des méthodes de classe «getByName» (pour travailler avec un serveur distant) ou «getLocalHost» (pour travailler en local):

ELS - 3 février 2003

Page 170: Programmation Objet Avec Java

POO avec Java - 162 - Etude de cas Une application Client-Serveur

public static InetAddress getByName(String host)throws UnknownHostException

Determines the IP address of a host, given the host'sname. The host name can either be a machine name, suchas «java.sun.com», or a string representing its IPaddress, such as «206.26.48.100».

Parameters:host - the specified host, or null for the local host.Returns:

an IP address for the given host name.Throws: UnknownHostException

if no IP address for the host could be found.

Ainsi, l'adresse IP peut être spécifiée sous la forme «aaa.bbb.ccc.ddd», ou alors par un nom composé: «machine.sous-domaine.domaine» en utilisant le système de noms (DNS, Data Name System) qui associe les numéros IP à des noms symboliques par le biais de serveurs de noms répartis sur le réseau.

Pour le test, il est conseillé de travailler en local, en exécutant l'application serveur sur la même machine que celle du client. La méthode «getLocalHost()» retourne l'adresse IP de la machine locale.

public static InetAddress getLocalHost()throws UnknownHostException

Returns the local host.

Returns:the IP address of the local host.

Throws: UnknownHostExceptionif no IP address for the host could be found

ELS - 3 février 2003

Page 171: Programmation Objet Avec Java

POO avec Java - 163 - Etude de cas Une application Client-Serveur

Exemple d'utilisation

InetAddress adresseIP;try {

// Adresse du serveur (exemple):

adresseIP = InetAddress.getByName("129.194.186.35");

// ou: travail en mode local (le serveur = machine locale)

adresseIP = InetAddress.getLocalHost();}

catch (UnknownHostException e) { /* adresse inconnue sur le réseau

*/ }

Fermeture de la connexionLa connexion sera fermée dès que l'un ou l'autre des deux partenaires aura fermé la soc-ket qui lui est associée en lui envoyant le message «close»:

uneSocket.close() ;

Dès que la connexion est fermée, toute tentative d'accès à la socket se soldera par une levée d'exception de type «IOException».

Ouverture simultanée de plusieurs connexionsPendant toute la durée d'une connexion (qui sera fermée indifféremment par le serveur ou par le client), le même serveur peut accepter des demandes de connexions d'autres clients, qui , - une fois établies -, se dérouleront et seront gérées en parallèle. On parle alors de «serveur concurrent».

En général, un tel serveur sera réalisé en confiant la gestion de chaque connexion à un thread indépendant.

ApplicationClient

ApplicationServeursocket

d'écoute"ServerSocket"

socket

socket

ApplicationClient

socket

socket

ELS - 3 février 2003

Page 172: Programmation Objet Avec Java

POO avec Java - 164 - Etude de cas Une application Client-Serveur

Transmettre et recevoir des informations au travers d'une socketToute socket met à disposition deux flux de données primaires: un «InputStream» en entrée, et un «OutputStream» en sortie.

Pour accéder à des deux différents flux, la classe «Socket» propose les méthodes:

InputStream getInputStream(): pour retourner le flux d'entrée,OutputStream getOutputStream(): pour retourner le flus de sortie.

Comme il s'agit de flux de bytes non bufferisés, le programmeur à tout intérêt à tra-vailler de manière plus évoluée en ouvrant des «flux de traitement» (flux de deuxième niveau) pour manipuler ces deux flux primaires.

Dans une première étape, si notre application communique par caractères plutôt que par bytes, on peut commencer par ouvrir des flux de conversion entre bytes et caractères.

Comme par exemple:

InputStreamReader isr = newInputStreamReader(socket.getInputStream())

OututStreamReader osr = newOutputStreamReader(socket.getOutputStream())

Pour envoyer des informations, le plus pratique est d'ouvrir un nouveau flux de traitement de type «PrintWriter» créé directement à partir du flux de bytes «OutputStream»:

PrintWriter pw = new PrintWriter (socket.getOutputS-tream());

En effet, les flux de type «PrintWriter» permettent aussi bien d'envoyer des textes, que des types primitifs, que des objets sérialisés.

Pour lire des informations, l'équivalent du flux «PrintWriter» n'existe pas. Pour la lecture de textes, on pourra utiliser le flux de type «BufferedRea-der». Pour la lecture de types primitifs (entiers, réels,..), plutôt utiliser le flux «DataInputStream». Le flux «BufferedReader» doit être créé à partir d'un «InputStreamReader», alors que le flux «DataInputStream» peut

SocketInputStream

OutputStreamInternet

bytes

bytesProgramme

ELS - 3 février 2003

Page 173: Programmation Objet Avec Java

POO avec Java - 165 - Etude de cas Une application Client-Serveur

être créé directement à partir d'un flux de bytes «InputStream».

BufferedReader bfr =new BufferedReader (

newInputStreamReader(socket.getInputStream())

);

DataInputStream dis =new DataInputStream(socket.getInputStream());

Voici, par exemple, une configuration qui permettrait de communiquer des informa-tions de type «Texte»:

6.3 ILLUSTRATION: LE PROGRAMME «CHAT» L'exemple proposé est une discussion sur internet permettant à plusieurs participants de communiquer entre eux par le biais de messages. Dès qu'un participant (un «client») en-voie un message au «serveur», ce dernier le renvoie à tous les participants à la discus-

SocketInputStream

OutputStreamInternet

bytes

bytes

Programme

InputStreamReader

PrintWritercaractèrestextesprimitifsobjets

BufferedReadercaractères

textes

ELS - 3 février 2003

Page 174: Programmation Objet Avec Java

POO avec Java - 166 - Etude de cas Une application Client-Serveur

sion.

6.3.1 Interface utilisateurNotre application comporte deux éléments distincts: le côté «Serveur» et le côté «Client».

Côté «Serveur»Du côté serveur, l'application Java s'exécute dans une fenêtre «DOS» - sur Windows NT -, ou dans une fenêtre de commande de type «Shelltool» - sur une station UNIX de type SUN-Sparc.

Dans l'état, une fois que le serveur est lancé, il n'est plus possible d'interagir avec lui, sinon en interrompant brutalement son exécution au moyen d'un «Control-C»

Le serveur se contente d'afficher des informations d'état: un nouveau client est con-necté, un client s'est déconnecté, ou encore de signaler des problèmes de communica-tion.

Pour contrôle, le serveur affiche en écho toutes les chaînes de caractères reçues en pro-venance des clients.

Côté «Client»L'application s'exécute dans une fenêtre graphique.

Client

Client

Client

Serveur

Réseau

Le serveur maintient une liste de connexions avec des clients.Cette liste est mise à jour dès qu'un nouveau client se connecte (ou se déconnecte)Les textes renvoyés sont identifiés par le sigle du client

ELS - 3 février 2003

Page 175: Programmation Objet Avec Java

POO avec Java - 167 - Etude de cas Une application Client-Serveur

L'image présentée ci-dessous donne un aperçu de l'exécution locale de 2 processus « Client».

.

Premier client à s'être con-necté avec le serveur. Ce client a envoyé une phrase alors qu'il était seul à être connecté. Le serveur lui a renvoyé cette phrase en retour. Ce client s'est attribué un sigle de reconnaissance: «AA».

Deuxième client connecté (client «BB»). Ce dernier envoie une phrase au serveur. Cette phrase est ren-voyée aux deux participants: lui-même et le pre-mier client.

Client BB Client AA

ELS - 3 février 2003

Page 176: Programmation Objet Avec Java

POO avec Java - 168 - Etude de cas Une application Client-Serveur

Le client dispose principalement de deux boutons:

Un bouton «Connecter-DéConnecter» lui permettant de d'ordonner une con-nexion avec le serveur, ou au contraire d'interrompre la connexion.Un bouton «Envoyer» pour envoyer le texte que le client a préalablement saisi dans le champ d'édition. Ce bouton n'apparaît dans la fenêtre que si l'applica-tion se trouve dans l'état «Connecté»

Le client dispose par ailleurs d'un champs d'édition, lui permettant de spécifier le texte qu'il désire communiquer aux autres participants. Ce champ d'édition peut être effacé en actionnant un troisième bouton: le bouton «Effacer».

Un champ de saisie lui permet de spécifier un sigle de deux caractères: ce sigle permet-tra de l'identifier dans la zone de «chat».

Le client dispose enfin de la zone «chat», munie d'un ascenseur. Dans cette zone seront affichés les textes renvoyés par le serveur. Les textes affichés comportent un préfixe permettant d'identifier leur créateur («sigle» personnel du client).

Les «messages système «indiquant des problèmes éventuels de connexion sont indi-qués dans le bas de la fenêtre.

L'image présentée ci-après montre l'état des clients et du serveur après déconnexion.

Fenêtre d'exécu-tion du serveur.En l'état, deux con-nexions ont été opérées. Deux chaînes de caractè-res ont été reçues et renvoyées.

ELS - 3 février 2003

Page 177: Programmation Objet Avec Java

POO avec Java - 169 - Etude de cas Une application Client-Serveur

6.3.2 Le «Client»: diagramme des classesLa mise en œuvre du client a nécessité l'écriture de 4 classes (les autres classes appar-tiennent à la librairie Java).

Le client «BB» s'est déconnecté.

Le message «FIN» a été envoyé au serveur.Le serveur ferme la socket en lui envoyant le mes-sage «close()».

La rupture de communication est alors repérée par l'activité « Serveur-Ecouteur» du client (Levée de l'exception IOEx-ception error).

Le premier client «AA» se déconnecte à son tour..

Côté serveur, on peut remarquer le message «Décon-nexion d'un client», message affiché lorsque l'objet actif responsable de la connexion avec le client est retiré de la liste des con-nexions (puis détruit par le «gar-bage collector» de Java).

ELS - 3 février 2003

Page 178: Programmation Objet Avec Java

POO avec Java - 170 - Etude de cas Une application Client-Serveur

Il s'agit des classes «Client», «ServeurEcouteur», «InterfaceUtilisateur» et «FenetreApplication».

Classe «Client»L'objet «Client» est un objet passif, responsable du traitement des 3 événements utili-sateurs:

1. demande de connexion ou de déconnexion avec le serveur ,

ButtonButton

Client: diagramme de classesInterfaceUtilisateur<<Runnable>>

- $ NO_PORT = 5000 <<final>>

- standAloneMode: boolean

- connexionEnCours: boolean

Client

Socketsocket connectée

au serveur

PrintWriter

Canal de sortie,

pour envoyer du

texte

<<constructor>>+ Client ()

<<applet methods>>+ init()+ destroy()+ paint(Graphics)+ start()+ stop()

<<standalone application methods>>+ $ main (args: String[])

<<gestion événements (du gui)>>+ ev_connecter()+ ev_déconnecter()+ ev_envoyer()+ terminer()

<<gestion événement (du "ServeurEcouteur")+ connexionInterrompue()

<<private methods>>- connecter(): boolean- envoyerTexteAuServeur(String)- fermerLaConnexion()

parent(unContainer)

TextF ie ld TextArea

2 1

Saisie:Sigle + messageà envoyer auserveur

affichage:textes envoyéspar le serveur

ButtonButton

écouteur

O utputStream

Flot de sortie,

pour envoyer des bytes

au serveur

Panel

Thread

ServeurEcouteur

+ ServeurEcouteur(InputStream,InterfaceUtilisateur,Client)

+ run ()

BufferedReader

Canal d'entrée,Pour lire du texte (bufferisé),à partir d'un flot associéde type caractère

InputStream

Flot d'entrée,Pour recevoir les bytesenvoyés par le serveur

socket connectéeau serveur

InputStreamReader

Passerelle entre un flot de "bytes"et un flot de caractères

Pour

lire

lesbytes

envoyés

par

le

serveur

Pour

envoyerles

informations(bytes)

au

serveur

Pour afficher les lignes de texte envoyéespar le serveur

+ getMessage(): String

+ getSigle(): String

+ appondreTexte(text: String)

+ effacerAvertissement()

+ afficherAvertissement()

+ laConnexionEstActive()

+ laConnexionEstNonActive()+ run() -- Afficher signal de connexion

<<Events handling>>+ actionPerformed(ev: ActionEvent)

"Effacer"

"Envoyer""Connecter"

ActionListener

<<implements>>

<<interface>>

<<implements>>

écouté

par

FenetreApplication

Frame

Container

insérée dans lafenêtre (cas d'unlancement en mode"Application autonome")

Fenêtre contenantl'applet (cas d'unlancement en mode"Application autonome")

contenu dans

Applet

pour

signaler

une

inerruption

de

la

connexion

gui

a_client

a_gui

flot d'entrée associé,

de type "caractère"

ELS - 3 février 2003

Page 179: Programmation Objet Avec Java

POO avec Java - 171 - Etude de cas Une application Client-Serveur

2. envoi d'un message avec le serveur,3. demande de déconnexion.

En réponse à une demande de connexion, le client:

crée une socket pour communiquer avec le serveur ;crée une instance de «ServeurEcouteur», un objet actif qui sera responsable d'écouter le serveur et de réceptionner ses messages.

Notons qu'une tentative de connexion est opérée automatiquement dès qu'une applica-tion «Client» est ouverte, sans attendre la pression du bouton «Connecter».

En réponse à un envoi de message, la chaîne de caractères saisie par l'utilisateur est envoyée au serveur.

En réponse à une demande de déconnexion, le texte «FIN» est envoyé au serveur.

Enfin, cet objet traite l'événement «connectionInterrupted», signalé par l'activité «ServeurEcouteur». Cet événement arrive normalement quand le serveur a fermé la connexion, suite à la réception du message «FIN».

Classe «ServeurEcouteur»Le «ServeurEcouteur» est un objet actif à l'écoute du serveur. Toutes les chaînes de caractères envoyées par le serveur sont affichées dans la zone d'affichage prévue à cet effet.

En cas de rupture de communication (le serveur a fermé la connexion), cette tâche se termine après avoir envoyé le message «connectionInterrupted» au client.

Classe «InterfaceUtilisateur»Cette classe met en œuvres les composants de l'interface graphique avec l'utilisateur (boutons, champ d'édition,..).

Les événements «Envoyer» et «Connecter-Déconnecter» sont communiqués à l'objet «Client».

Le bouton «Effacer» (effacement du champ d'édition) est directement traité par l'interface utilisateur.

Classe «FenetreApplication»Représente la fenêtre graphique dans laquelle s'exécute l'application (si le programme est exécuté en mode «Application autonome»). Cet objet est à l'écoute de l'événement relatif à la fermeture de la fenêtre par le client. Dans ce cas, un message est envoyé au client afin de fermer proprement la communication.

ELS - 3 février 2003

Page 180: Programmation Objet Avec Java

POO avec Java - 172 - Etude de cas Une application Client-Serveur

6.3.3 Le «Serveur»: diagramme des classesLa mise en œuvre du serveur a nécessité l'écriture de 3 classes (les autres classes appar-tiennent à la librairie Java).

Il s'agit des classes «Serveur», «Connexion» et «ControleurConnexions».

Serveur: diagramme de classes

- $ NO_PORT = 5000 <<final>>

Serveur

<<consttructeur>>+ Serveur ()

<<public methods>>+ $ main (args: String [])

<<activity method>>+ run()

Runnable<<interface>>

<<implements>>

Thread

activité concurrenteassociée

Exécute

la

méthode

run()

ServerSocket

Un serveur de socket attend une demande de connexionen provenance du réseau.En réponse à une demande, le serveur crée une socketqui sera utilisée pour communiquer avec le client.

Serveur de sockets,à lécoute du port 5000

Vector

Connexion

liste des connexions

0..50

ControleurConnexions

Pour

enlever

les

connexions

inactives

Si le thread associé à une connexionn'est plus actif, la connexion correspondanteest enlevée de la liste des connexions.

broadcasts

atext

to

every

connection

Socketsocket connectée

au client

O utputStream

Flot de sortie,

pour envoyer des bytes

au client

InputStream

Flot d'entrée,Pour recevoir des bytesenvoyés par le client

socket connectéeau client

BufferedReader

Canal d'entrée,Pour lire le texteenvoyé par le client

InputStreamReader

Passerelle entre un flot de"bytes" et un flot decaractères

Pour lire lescaractères envoyéspar le client

Pour

lire

des

bytes

PrintWriter

Canal de sortie,

pour envoyer du texte

uniquement

Pour

envoyer

des

bytes

Pour réveiller le contrôleur:une connexion a été interrompue

Thread

+ ControleurConnexions (laListeDesConnexions)

+ run()- enlèveConnexionsInactives()

+ Connexion (uneSocket,leControleurConnexions,laListeDesConnexions)

+ run()- broadcast (message: String)

$ DUREE_DE_VEILLE = 10 sec

ELS - 3 février 2003

Page 181: Programmation Objet Avec Java

POO avec Java - 173 - Etude de cas Une application Client-Serveur

Classe «Serveur»Le serveur est un objet actif (associé à thread).

Le serveur attend des demandes de connexions.

A cet effet, une instance de type «ServerSocket» est créée. Cette socket, associée à un No de port spécifique, reçoit le message «accept()». Elle se met alors à l'écoute du réseau en attendant une connexion sur le port correspondant.

Dès qu'une demande de connexion est détectée, la «socket serveur» crée une socket de connexion qui permettra au serveur de communiquer avec le client.

Suite à une demande de connexion, le serveur crée une instance de type «Connexion», un objet actif qui sera responsable de la communication avec le client (envoi et récep-tion). Cet objet sera rajouté à une liste dynamique collectant toutes les connexions en cours.

Le serveur crée également une instance de type «ControleurConnexions», objet actif qui sera responsable de retirer de la liste dynamique toute connexion inactive.

Classe «Connexion»Une instance pour chaque connexion avec le serveur !

Une «Connexion» est un objet actif responsable de la communication avec le client associé.

L'activité associée attend qu'un message soit envoyé par le client. Ce message est alors renvoyé à tous les participants.

Pour effectuer ce renvoi, cet objet parcourt la liste dynamiques de toutes les connec-tions en cours.

Cette liste est un objet critique. Elle est accédée en exclusion mutuelle (bloc «synchro-nized» en Java ). En effet, cette liste est susceptible d'être accédée en lecture-écriture concurremment par plusieurs activités: le serveur, les connexions en cours et le «Con-troleurConnexions».

Cet objet détecte la fin d'une communication (réception du message «FIN»). Il termine alors son activité après avoir signalé l'événement à l'objet «ControleurConnexions».

Classe «ControleurConnexions»L'objet de type «ControleurConnexions «existe en un seul exemplaire. Il s'agit d'un objet actif.

A la réception du message «finDeConnexion» (envoyé par un objet de type «Con-nexion»), cet objet retire cette connexion de la liste dynamique des connexions, accé-dée en exclusion mutuelle.

ELS - 3 février 2003

Page 182: Programmation Objet Avec Java

POO avec Java - 174 - Etude de cas Une application Client-Serveur

6.3.4 Test de l'applicationLe test peut être opéré de deux manières:

1. Localement sur une seule et unique station: chaque processus «client» se con-necte à une adresse IP correspondant au «Local host».Le serveur est lancé sur la même station.

2. En lançant un processus «Serveur» sur un serveur HTTP. Ce processus s'exécu-te en permanence et attend des demandes de connexions de la part de clients au travers du réseau Internet.On peut remarquer, dans ce deuxième cas, que la communication présente des temps de réponse extrêmement variables en cours de communication. La plu-part du temps, le temps de réponse est quasi-instantané. Toutefois, il arrive par-fois que quelques secondes soient nécessaires avant qu'un «Client» ne reçoive son texte en retour.Ceci dépend de la charge de la charge du réseau et du mode de fonctionnement du fournisseur de service (côté «Client») et du serveur http (côté «Serveur»).

6.3.5 Risque d'interblocageLe fait que chaque client dispose de deux activités, une pour l'envoi de messages et l'autre pour la réception de messages, facilite l'élimination du risque potentiel d'interblo-cage.

Des diagrammes d'état et de transitions donnés en annexe, nous pouvons élaborer les 5 diagrammes simplifiés présentés ci-après. Ces 5 diagrammes correspondent chacun à l'une des 5 activités concurrentes de l'application. Ils ne représentent que les endroits où les threads sont bloqués dans l'attente d'un événement.

ELS - 3 février 2003

Page 183: Programmation Objet Avec Java

POO avec Java - 175 - Etude de cas Une application Client-Serveur

Une analyse détaillée de chacun des états bloquants montre qu'aucun d'entre eux n'est bloqué définitivement.

6.4 ANNEXESA0: Le protocole Client - ServeurA1: Le «Client»: diagramme des états et transitionsA2: Le «Serveur»: diagramme des états et transitionsA3: Listing du fichier Client: «Client.java»A4: Listing du fichier Client: «ServeurEcouteur.java»A5: Listing du fichier Client: «InterfaceUtilisateur.java»A6: Listing du fichier Serveur: «Serveur.java»A7: Listing du fichier Serveur: «Connexion.java»A8: Listing du fichier Serveur: «ControleurConnexions.java»A9: Classe «Socket»A10: Classe «ServerSocket»A11: Classe «InetAddress»

thread "Serveur"

Attendre une demande de connexion

thread "Une connexion"

Attendre uneentrée du client

Entrée dans le moniteur de la liste des connexions

Renvoyer le texte du clientà chacune des connexions

Quitter le moniteurde la liste des connexions

thread "ControleurConnexions"

Attendre un signalde "fin de connexion"

Client

Attendre la pressiondu bouton "Envoyer"

Envoyer la ligne de texteau serveur

thread "ServeurEcouteur"thread "Client"

Serveur

Quitter le moniteurde la liste des connexions

Entrée dans le moniteur de la liste des connexions

(pour ajouter lanouvelle connexion)

Quitter le moniteurde la liste des connexions

Entrée dans le moniteur de la liste des connexions

(pour enleverla connexion)

Attendre uneentrée texte du serveur

Afficher le texte dans la fenêtre du client

ELS - 3 février 2003

Page 184: Programmation Objet Avec Java

POO avec Java - 176 - Etude de cas Une application Client-Serveur

A0: Protocole Client - Serveur

On peut distinguer 4 étapes:

1. Etat «Non connecté»> Etablissement de la connexionLe client ouvre une socket qui sera connectée avec une socket correspondant côté serveurEn cas de succès, le client passe à l'état «Connexion en cours», sinon, reste à l'état «Non connecté».

2. Etat «Connexion en cours»> Etablissement d'une sessionLe client envoie la commande LOGIN au serveur, accompagnée de son identifi-cateur personnel.Le serveur répond par LOGIN_OK en cas d'acceptation, par LOGIN_ERROR en cas de refus.En cas de succès, le client pass à l'état «Session en cours».En cas de refus, le client démarre une «fermeture de session» (voir étape no 4).

3. Etat «Session en cours»> Echange de lignes de texteLe client envoie des lignes de texte au serveur. Ce dernier les diffuse à tous les clients connectés, y compris au client qui en est à l'origine.

4. Etat «Session en cours»> Fermeture de sessionLe client envoie la commande LOGOUT au serveur. Ce dernier ferme alors la socket (côté serveur).La fermeture de la socket serveur est détectée aussitôt au niveau client: la sai-sie de la prochaîne ligne de texte en provenance du serveur retourne la valeur «null».Le client ferme alors sa propre socket, et repasse à l'état «Non connecté»

Syntaxe des messagesDeux types de messages: les messages CTR pour le contrôle et les messages DATA pour

ELS - 3 février 2003

Page 185: Programmation Objet Avec Java

POO avec Java - 177 - Etude de cas Une application Client-Serveur

l'échange

# CTR

DATA

#

# Ligne de texteCaractères de fin de ligne ('\n')remplacéspar des caractères nuls

LOGIN

LOGOUT

# Id clientClient Serveur

Serveur Client

# CTR

DATA

#

# Ligne de texte

LOGIN_OK

LOGIN_ERROR

#Id source

Message d'erreur#

Caractères de fin de ligne ('\n')remplacéspar des caractères nuls

ELS - 3 février 2003

Page 186: Programmation Objet Avec Java

POO avec Java - 178 - Etude de cas Une application Client-Serveur

A1: Le «Client»: diagramme des états et transitions

Clie

nt d

écon

nect

é,en

att

end

re d

'une

act

ion

de l'

utili

sate

ur

Eta

blis

sem

ent

de la

con

nexi

on

"Connecter"

IOException

error

/fermer

la

socket

IOException

error

/^client:

connectionInterrupted()

thre

ad a

ssoc

ié à

l'éc

oute

du

serv

eur

Att

endr

e un

e lig

ne d

e t

exte

du

ser

veur

do:

Créer

unesocket

new

Socket

(AddressIp,

NO_PORT)

exit: Ouvrir

uncanal

d'entrée

de

type

"Texte"

avec

le

flot

d'entrée

de

la

socket,

Ouvrir

demêmer

un

canal

de

sortie

detype

"Texte"

Créer

lethread

àl'écoute

du

serveur

do:

^inputCanal:

readLine()

Ligne

de

texte

reçue

/^displayArea:

display(textline)

"Quitter"

(del'utilisateur)

/"FIN"

envoyé

au

serveur

CLI

EN

T:

Dia

gram

me

d'ét

ats

Le C

lient

est

con

nect

é au

ser

veur

Att

endr

e un

e ac

tion

de

l'uti

lisat

eur,

o

u un

sig

nal i

ndiq

uant

une

rup

ture

de

conn

exio

n

"Quitter"

"Envoyer"

(del'utilisateur)

Ligne

de

texte

envoyée

Env

oi

do:

envoi

d'une

ligne

de

texte

auserurve

IOException

error

/fermer

la

socket

Connexion

interrompue

(événement

communiqué

par

le

thread

àl'écoute

du

serveur)

/fermer

la

socket

thre

ad a

ssoc

ié a

u cl

ient

"DéConnecter"(de

l'utilisateur)

/"FIN"

envoyé

au

serveur

ELS - 3 février 2003

Page 187: Programmation Objet Avec Java

POO avec Java - 179 - Etude de cas Une application Client-Serveur

A2: Le «Serveur»: diagramme des états et transitions

Cré

er u

ne n

ouve

lle "

Con

nexi

on"

Demande

de

connexion/

Créer

une

nouvelle

socket

IOException

error/fermer

la

socket

IOExceptionerror

Créer

lethread

responsable

de

la

gestion

de

la

connexion

thre

ad d

u s

erve

ur

thre

ad a

ssoc

ié à

une

"C

onne

xion

"

Att

endr

e un

e lig

ne d

e te

xte

du c

lient

entry:

ouvrirdeux

canaux;

le

premier

avec

leflot

d'entrée

de

la

socket,

et

le

2ème

avec

le

flot

de

sortie

do:

^inputTextCanal:

readLine()

Ligne

de

texte

reçue

do:

pour

chacune

des

connexions,

écrire

le

texte

dans

le

canal

de

sortie

de

la

socket

exit:

quitterle

moniteur

Ren

voye

r le

tex

teà

chaq

ue c

lient

thre

ad a

ssoc

ié a

u "c

ontr

ôleu

r de

s co

nnex

ions

"

un th

read

indé

pend

ant p

arco

nnex

ion

/créer

le

thread

du

contrôleur

des

connexions

Att

endr

e un

sig

nal d

e "F

in d

e co

nnex

ion"

do:

attendre

unsignal

"FIN",

au

maximum

pendant

10

sec.

[texte

"FIN"reçu]

Ter

min

er

la c

onne

xion

do:

fermer

lasocket

^controller:

endOfConnection(this)

"Fin

de

connexion",

ou

délai

10

sec.

échu

IOExceptionerror

SE

RV

EU

R:

Dia

gram

me

d'ét

ats

Att

endr

e un

e de

man

dede

con

nexi

on

sur

le p

ort

5000

do:

^serverSocket:

accept()

Application

Serveur

quittée

do:

Entrerdans

le

moniteur

de

la

liste

des

connexions,

et

ajouter

la

nouvelle

connexion

exit:

quitterle

moniteur

de

la

liste

des

connexions

Enl

ever

la c

onne

xion

inac

tive

do:

Entrer

dansle

moniteur

de

la

liste

desconnexions,

et

enlever

la

connexion

exit:

quitterle

moniteur

do:

entrer

dansle

moniteur

de

la

liste

des

connexions

ELS - 3 février 2003

Page 188: Programmation Objet Avec Java

POO avec Java - 180 - Etude de cas Une application Client-Serveur

A3: Listing du fichier Client: «Client.java»import java.applet.*;import java.awt.*;import java.net.*;import java.awt.event.*;import java.io.*;import java.util.StringTokenizer;

//********************************************************************// Client.java// E.Lefrançois 10 février 2001// Projet «Chat», avec soquettes//********************************************************************

//====================================================================// classe "FenetreApplication"// --> Support pour l'application exécutée en mode "Standalone" (// Application autonome)//// Cette classe agit en tant que fenêtre "top level" dans laquelle// s'exécute l'applet.//======================================================================class FenetreApplication extends Frame {// Classe imbriquée utilisée pour traiter les événements "fenêtre", comme// "window closing" notamment.

class WindowEventListener extends WindowAdapter {public void windowClosing (WindowEvent e) {

a_client.ev_deconnecter();System.exit(0);

}}

// Variables d'instanceprivate WindowEventListener wEL = new WindowEventListener();

/*** @supplierRole a_client*/private Client a_client; // Client associé

// Constructeur (s)public FenetreApplication (Client leClient) {

setTitle (" C L I E N T - C H A T ");addWindowListener (wEL);setBackground(Color.lightGray);a_client = leClient;

}}

======================================================================// Classe Client// Attend des événements utilisateurs:// 1) "connecter": demande de connexion demandée// par l'utilisateur// 2) "envoyer": un message doit être envoyé aux autres clients// 3) "deconnecter": demande de déconnexion demandée par l'uti// lisateur//

ELS - 3 février 2003

Page 189: Programmation Objet Avec Java

POO avec Java - 181 - Etude de cas Une application Client-Serveur

// Attend des événements générés par le serveur// 1) "sessionAcceptee": la demande de session a été acceptée// 3) "sessionRefusee": la demande de session a été refusée// 2) "connexionFermee": la connexion est terminée, et la session// par la même occasion// Causes possibles (non distinguées)// - Normale: suite à demande de déconnexion demandée par// l'utilisateur// - Anormale: le serveur est "tombé\", problème d'I/O//

// En réponse à une demande de connexion de l'utilisateur:// 1/ une socket est créée afin de pouvoir communiquer avec le serveur.// 2/ le programme crée une instance de la classe "Connexion", un objet// actif qui sera responsable de la réception et de l'analyse des// messages envoyés par le serveur.//// En réponse à une demande d'envoi, la ligne de texte spécifiée par// l'utilisateur est envoyée au serveur.//// En réponse à une demande de déconnexion, ou lorsque l'application est// "quittée", le message de fin de session est envoyé au serveur. C'est// le serveur lui-même qui fermera la socket.======================================================================

// Fenêtre "Top-level" contenant l'applet// Utilisé uniquement en mode "standalone"

/*** @link aggregationByValue*/

private InterfaceUtilisateur gui;// Définition de l'interface utilisateur (boutons,..)

/*** @supplierRole socket* @link aggregationByValue*/

private Socket socket; // socket connectée au serveur

/*** @supplierRole ecouteurDuServeur* @link aggregationByValue*/

private ServeurEcouteur ecouteurDuServeur;// Objet actif associé, qui écoute le serveur de manière// concurrente au reste du programme

/*** @supplierRole canalDeSortie*/

ELS - 3 février 2003

Page 190: Programmation Objet Avec Java

POO avec Java - 182 - Etude de cas Une application Client-Serveur

private PrintWriter canalSeSortie; // Canal de sortie, pour le texte// envoyé au serveur

private DataOutputStream dataOutCanal;// Canal de sortie pour les données uniquement: non utilisé !// Pour le créer:

// dataOutCanal = new DataOutputStream (socketWithServer.getOutputStream());

// Constructeur(s)public Client() {}

// Méthodes publiquespublic static void main(String[] args) {// Point d'entrée de l'application, si exécutée en mode "standalone"

// Création et démarrage de l'appletClient appletClient = new Client();appletClient.standAloneMode = true;

// Création de la fenêtre "top-level", dans laquelle s'exécute l'applet Clientfenetre = new FenetreApplication(appletClient);fenetre.add("Center", appletClient);

appletClient.init();fenetre.pack(); // Ajustement de la taille de la fenêtre à la taille

// de ses composantsfenetre.show();appletClient.start();

}

public void init() {// Méthode appelée lorsque l'applet est chargée pour la première fois, ou// lorsqu'elle est rechargée.// Cette méthode est utilisée pour opérer diverses initialisations// (structures de données, composants de la fenêtre,..)

gui = new InterfaceUtilisateur(this);setLayout (new BorderLayout());add (gui, "Center");gui.desactiverEtatConnexion();gui.desactiverEnvoi();

}

public void start() {// Méthode appelée lorsque l'applet est initialisée (suit le message init()),// et à chaque fois que la page qui contient l'applet est à nouveau activée

gui.effacerAvertissement();if (! connexionEnCours) demarrerSession();

}

public void stop(){}// Méthode appelée lorsque la page qui contient l'applet est désactivée// Le chat continue en arrière plan

ELS - 3 février 2003

Page 191: Programmation Objet Avec Java

POO avec Java - 183 - Etude de cas Une application Client-Serveur

public void ev_connexionFermee (String message) {// Evénement envoyé par l'écouteur du serveur

gui.afficherAvertissement("CONNEXION TERMINEE",message);fermerSession();

}

// Gestion des événementspublic void ev_connecter () {// Demande de connexion demandée par le client

gui.effacerAvertissement();demarrerSession();

}

public void ev_deconnecter () {// Fin de connexion demandée par le client

gui.effacerAvertissement();seDeconnecter();

}

public void ev_envoyer() {if (gui.getMessage().length()!=0)

envoyerTexteAuServeur("#DATA#"+gui.getMessage());}

// Evénementspublic void ev_sessionAcceptee() {// Evénement envoyé par le serveur (Via écouteur de serveur)

sessionEnCours = true;gui.activerEnvoi();

}

public void ev_sessionRefusee(String message) {// Evénement envoyé par le serveur (Via écouteur de serveur)

gui.afficherAvertissement("!! SESSION REFUSEE !! ",message);

seDeconnecter();}

// Méthodes diversespublic String getIdClient () {return monId; }public void setIdClient(String id) {monId = id; }

// Méthodes privéesprivate void envoyerTexteAuServeur (String text) {

if (! connexionEnCours) return;String t = text.replace('

try {canalSeSortie.println (t);

canalSeSortie.flush();// le canal a été créé sans "automatic flush"}catch (Exception e) {

gui.afficherAvertissement("!! PROBLEME DE CONNEXION !! " ,

ELS - 3 février 2003

Page 192: Programmation Objet Avec Java

POO avec Java - 184 - Etude de cas Une application Client-Serveur

e.toString());fermerSession();

}}

private String saisirIdentificateurClient () {monId = null;LoginDialogue ld = new LoginDialogue

((Frame)getParent(), this);ld.show();return monId;

}

private void demarrerSession() {String id = saisirIdentificateurClient();if (id != null) {

connexionEnCours = seConnecter();if (connexionEnCours) {

gui.activerEtatConnexion();envoyerTexteAuServeur("#CTR#LOGIN#"+id);

}}

}

private void fermerSession() {connexionEnCours = false;sessionEnCours = false;gui.desactiverEtatConnexion();gui.desactiverEnvoi();if (socket != null)try {socket.close();} catch (IOException e ) {}

}

private void seDeconnecter() {if (connexionEnCours) { // Programmation défensive

envoyerTexteAuServeur ("#CTR#LOGOUT");}

}

private boolean seConnecter () {// Retourne "true" si la connexion est établie, "false" dans les autres cas

if (connexionEnCours) return true;

InetAddress adresseIP;//------------ Essayer l'adressetry {

//adresseIP = InetAddress.getByName("xxx.xxx.xxx.xxx");adresseIP = InetAddress.getLocalHost();

// Connexion en local//System.out.println( adresseIP.getHostAddress());//System.out.println( adresseIP.getHostName());

}catch (UnknownHostException e) {

gui.afficherAvertissement("!! PROBLEME DE CONNEXION !!", e.toString());

ELS - 3 février 2003

Page 193: Programmation Objet Avec Java

POO avec Java - 185 - Etude de cas Une application Client-Serveur

return false;}//------------- Création de la socket permettant de communiquer avec// le serveurtry {

socket = new Socket (adresseIP, NO_PORT);}catch (IOException e) {

gui.afficherAvertissement("!! PROBLEME DE CONNEXION !!" ,e.toString());

return false;}//------------- Ouverture des canaux d'entrée et de sortietry {

canalSeSortie = new PrintWriter(socket.getOutputStream());

ecouteurDuServeur = new ServeurEcouteur(socket.getInputStream(),gui,this);

return true; // Connexion OK}catch (IOException e) {

try {socket.close(); } catch (IOException ioe) {};gui.afficherAvertissement

("!! PROBLEME DE CONNEXION !!", e.toString());return false;

}}

}

ELS - 3 février 2003

Page 194: Programmation Objet Avec Java

POO avec Java - 186 - Etude de cas Une application Client-Serveur

A4: Listing du fichier Client: «ServeurEcouteur.java»//*************************************************************// ServeurEcouteur.java// E.Lefrançois 10 février 2001// Projet "Chat"//*************************************************************import java.io.*;import java.awt.*;import java.net.*;import java.util.StringTokenizer;

//=============================================================// Classe "ServeurEcouteur"// Activité concurrente dont le rôle consiste à écouter toutes// les informations envoyées par le serveur.// La connexion avec le serveur est contrôlée de manière// permanente. En cas de problème, cette activité affiche un// message d'avertissement et stoppe son exécution.//=============================================================public class ServeurEcouteur extends Thread {// Variables d'instance

/*** @supplierRole canalDentree*/

private BufferedReader canalDentree; // Canal d'entrée avec le serveur

/*** @supplierRole a_gui*/

private InterfaceUtilisateur a_gui; // Interface utilisateur (boutons,// zones d'affichage)

/*** @supplierRole a_client*/private Client a_client; // client associé

// Constructeur(s)public ServeurEcouteur ( InputStream is,

InterfaceUtilisateur gui,Client client) {

a_client = client;a_gui = gui;

try {canalDentree = new BufferedReader (

new InputStreamReader(is));this.start(); // Pour démarrer l'activité concurrente

}catch (Exception e) {

a_gui.afficherAvertissement

ELS - 3 février 2003

Page 195: Programmation Objet Avec Java

POO avec Java - 187 - Etude de cas Une application Client-Serveur

("!! PROBLEME DE CONNEXION !!",e.toString());

}}

// Code de l'activité concurrentepublic void run () {

String message = "";String ligneRecue;try {

while (true) {ligneRecue = canalDentree.readLine();

if (ligneRecue == null) { // Connexion interrompuemessage = "Terminaison normale";

// par le serveurbreak; // ("null"signale la fin d'un stream)

}analyserLigneRecue(ligneRecue);

}}

catch (IOException e) { message = e.toString(); }finally {

a_client.ev_connexionFermee (message);}

}

private void analyserLigneRecue(String ligne) {StringTokenizer st = new StringTokenizer (ligne, "#");try {

String typeInfo = st.nextToken();if (typeInfo.equals("CTR")){

String info = st.nextToken();if (info.equals("LOGIN_OK"))

a_client.ev_sessionAcceptee();else if (info.equals("LOGIN_ERROR"))

a_client.ev_sessionRefusee(" !! SESSION REFUSEE !! " +st.nextToken());

else if (info.equals("CONNEXION_FERMEE"))a_client.ev_connexionFermee

("FIN SESSION " + st.nextToken());else signalerErreur(ligne);

}else if (typeInfo.equals("DATA")){

String idSource = st.nextToken();a_gui.afficherDansZoneChat (idSource,

st.nextToken());}else signalerErreur(ligne);

}catch (Exception e) {signalerErreur(ligne);}

}

private void signalerErreur (String ligneRecue) {a_gui.afficherAvertissement

ELS - 3 février 2003

Page 196: Programmation Objet Avec Java

POO avec Java - 188 - Etude de cas Une application Client-Serveur

(" !! INFORMATION RECUE ERRONEE !! ", ligneRecue);}

ELS - 3 février 2003

Page 197: Programmation Objet Avec Java

POO avec Java - 189 - Etude de cas Une application Client-Serveur

A5: Listing du fichier Client: «InterfaceUtilisateur.java»//*************************************************************// InterfaceUtilisateur.java// E.Lefrançois 10 février 2001// Projet "Chat//*************************************************************import java.awt.*;import java.awt.event.*;import java.util.*;/////////////////////////////////////////////////////////////////=============================================================// classe LoginDialogue// Saisie de l'identificateur du client//=============================================================//-------------------------------------------------------------

class LoginDialogue extends Dialog {/*** @supplierRole a_controleur*/private Client a_controleur;

// Controleur associé (communication d'ev.)private TextField tf = new TextField (6);private Label message = new Label();private Button bOk = new Button ("OK");private Button bAnnuler = new Button ("Annuler");

class Ecouteur implements ActionListener {private Window w; // fenêtre associéepublic Ecouteur (Window w) {this.w = w;}public void actionPerformed(ActionEvent e) {

if (e.getSource()==bAnnuler) { w.dispose();}else {

if (tf.getText().length() == 0) {message.setText("Au moins 1 caractère !!");

}else {

a_controleur.setIdClient(tf.getText());w.dispose();

}}

}}

public LoginDialogue(Frame parent, Client controleur) {super (parent, "Chat - Ouverture Session");setModal(true);a_controleur = controleur;Label l_login = new Label

("Votre identificateur (6 car. max)");

message.setForeground (Color.red);tf.addKeyListener( new KeyAdapter() {public void keyReleased(KeyEvent e) {

String s = tf.getText();if (s.length()> 6) {tf.setText(s.substring(0,6));

ELS - 3 février 2003

Page 198: Programmation Objet Avec Java

POO avec Java - 190 - Etude de cas Une application Client-Serveur

tf.setCaretPosition(7);}

}});Ecouteur ec = new Ecouteur (this);bOk.addActionListener(ec);bAnnuler.addActionListener (ec);Panel p1 = new Panel ();p1.add(l_login);p1.add(tf);add (p1, "North");Panel p2 = new Panel();p2.add (bOk);p2.add(bAnnuler);add (p2, "Center");add (message, "South");pack();

}}

/////////////////////////////////////////////////////////////////=============================================================// classe InterfaceUtilisateur// Spécification de la liste des composants graphiques// appartenant à l'interface utilisateur- Ecoute des événements// à un premier niveau//=============================================================

class InterfaceUtilisateur extends Panelimplements Runnable {

// Variables d'instanceprivate Thread activite; // Affichage "mobile", indiquant que la

// connexion est valide

/*** @supplierRole a_controleur*/

private Client a_controleur; // Ecouteur des événements (client)

// Déclaration des composants à visibilité globaleprivate TextArea ta_message = new TextArea ("", 3, 40);private Label l_invite = new Label ("Message de ******");

private boolean connexionActive; // Etat de la connexionprivate Button b_connexion = new Button ("Se connecter.. ");private Button b_envoyer = new Button ("Envoyer");private Label l_temoinConnexion; // Témoin de fonctionnement// Message d'avertissementprivate Label l_messageAvertissement = new Label(""),

l_detailAvertissement = new Label("");

ELS - 3 février 2003

Page 199: Programmation Objet Avec Java

POO avec Java - 191 - Etude de cas Une application Client-Serveur

private TextArea ta_chat = new TextArea("", 10, 80); // 10 lignes, 80 col.

// Constructeurpublic InterfaceUtilisateur (Client controleur){

a_controleur = controleur;

// Spécification de la fonte (applicable uniquement en mode "Applet")Font OldFnt = getFont();if (OldFnt != null) {

int newFontSize = 12;Font NewFnt = new Font(OldFnt.getName(), OldFnt.getStyle(),

newFontSize);setFont(NewFnt);

}

// Spécification du gestionnaire de présentationsetLayout(new BorderLayout(0, 5));

// Emplacement des panelsPanel p_ms = new Panel(); // zone "Envoi de message"Panel p_cc = new Panel(); // zone "Chat collectif"Panel p_cx = new Panel(); // zone "Contrôle connexion"add (p_ms, "North");add (p_cc, "Center");add (p_cx, "South");

// Panel - Envoi du messageButton b_effacer = new Button ("Effacer");b_effacer.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {ta_message.setText("");

}});desactiverEnvoi();b_envoyer.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {

a_controleur.ev_envoyer();}

});

p_ms.setLayout(new BorderLayout());Panel p_ms1 = new Panel(new FlowLayout

(FlowLayout.LEFT, 10, 5));p_ms.add (p_ms1, "North");p_ms.add (ta_message);

p_ms1.add (l_invite);p_ms1.add (b_envoyer);p_ms1.add (b_effacer);

// Panel - Zone Chat CollectifLabel l_titreZoneChat = new Label

ELS - 3 février 2003

Page 200: Programmation Objet Avec Java

POO avec Java - 192 - Etude de cas Une application Client-Serveur

("Zone Chat collective");p_cc.setLayout (new BorderLayout());p_cc.add (l_titreZoneChat, "North");p_cc.add (ta_chat, "Center");

// Panel - Contrôle Connexionl_temoinConnexion = new Label ();l_temoinConnexion.setForeground (Color.red);b_connexion.addActionListener(new ActionListener() {public void actionPerformed(ActionEvent e) {

if (!connexionActive) {a_controleur.ev_connecter();

}else a_controleur.ev_deconnecter();

}});p_cx.setLayout(new BorderLayout());p_cx.add(l_temoinConnexion, "Center");p_cx.add (b_connexion, "East");Panel p_avertissement = new Panel(new BorderLayout());p_avertissement.add (l_messageAvertissement, "North");p_avertissement.add (l_detailAvertissement, "South");p_cx.add (p_avertissement, "South");

}

// Public methodspublic void run () {String txt = new String(">> " +

" " +" " +" " +" " +" ");

while (activite == Thread.currentThread()) {try {Thread.sleep(100);} catch (Exception e){};txt = new String (txt.charAt(59)+ txt);txt = txt.substring (0,60);l_temoinConnexion.setText (txt);

}}

public void desactiverEtatConnexion() {connexionActive = false;activite = null;b_connexion.setLabel("Se connecter.. ");

}

public void activerEtatConnexion() {connexionActive = true;b_connexion.setLabel("Se déconnecter");if (activite == null) {activite = new Thread(this);activite.start();

ELS - 3 février 2003

Page 201: Programmation Objet Avec Java

POO avec Java - 193 - Etude de cas Une application Client-Serveur

}}

public void activerEnvoi() {l_invite.setText

("Message de " + a_controleur.getIdClient());b_envoyer.setEnabled(true);b_envoyer.setBackground (Color.green);

}

public void desactiverEnvoi() {l_invite.setText ("Message de ******");b_envoyer.setEnabled(false);b_envoyer.setBackground (Color.gray);

}

public void afficherAvertissement (String message,String detail) {

l_messageAvertissement.setText (message);l_detailAvertissement.setText (detail);

}

public void afficherDansZoneChat (String sourceId,String message) {

ta_chat.append (sourceId);StringTokenizer st = new StringTokenizer

(message, ""+(char)0);while (st.hasMoreTokens()){

ta_chat.append (''+ st.nextToken()+'

}}

public void effacerAvertissement() {l_messageAvertissement.setText ("");l_detailAvertissement.setText ("");

}

public String getMessage () {// Retourner le message à envoyer au serveur

return ta_message.getText();}

ELS - 3 février 2003

Page 202: Programmation Objet Avec Java

POO avec Java - 194 - Etude de cas Une application Client-Serveur

A6: Listing du fichier Serveur: «Serveur.java»//*************************************************************// Server.java// E.Lefrançois 1er janvier 2000// Projet "Chat"//*************************************************************import java.util.*;import java.net.*;import java.lang.System;import java.io.*;

// Pour accès à SQLimport java.sql.*;import java.net.URL;

//=============================================================// Application de type "Standalone"// Le serveur est un objet actif.// Cet objet attend des requêtes de connexion. A cette fin, une// instance de "ServerSocket" est créée. Cette insatnce attend// une demande de connexion et retourne une socquette associée à// cette connexion.// Une fois que la socquette est créée, le programme crèe une// instance de "Connection". Cette instance sera responsable de// la communication avec le client. Cette "Connection" est// ajoutée à la liste dynamique rassemblant toutes les// connexions.//// Enfin, le serveur crèe une instance de "ConnectionsControl// ler", un objet responsable de contrôler périodiquement cha// cune des connexions, et de retirer de la liste toute con// nexion qui serait repérée inactive.//=============================================================public class Serveur implements Runnable {

private Thread activité = null; // Activité associée

private Vector listeDesConnexions; // Liste des connexions avec// les clients

private ServerSocket socketServeur;// La "socketServeur" attend des demandes de connexions opérées// sur le no de port associé.// La taille de la queue d'entrée est de 50 (nombres max. de demandes// de connexion en attente).

private ControleurConnexions leControleurDesConnexions;// Pour contrôler périodiquement// l'activité de chaque connexion

private static final int NO_PORT = 5002;// No de port associé à ce service

// Constructeurpublic Serveur() {

listeDesConnexions = new Vector();

ELS - 3 février 2003

Page 203: Programmation Objet Avec Java

POO avec Java - 195 - Etude de cas Une application Client-Serveur

leControleurDesConnexions = newControleurConnexions (listeDesConnexions);

try {socketServeur = new ServerSocket (NO_PORT);

}catch (IOException e) {

System.err.println (e.getMessage());System.exit(1); // Status == "1" pour signaler

// une terminaison anormale de l'application}activité = new Thread (this);activité.start();

}

// Méthodes publiquespublic static void main (String args[]) {

new Serveur();}

public void run () {System.out.println

("Attend une nouvelle connexion...........");while (true) {

try {// Attendre une nouvelle connexionSocket nouvelleSocket =

socketServeur.accept();// Attend qu'une demande de connexion soit reçue par// cette socket, et accepte cette demande// Cette méthode bloque l'activité jusqu'à ce qu'une// connexion soit faite

Connexion nouvelleConnexion =new Connexion (nouvelleSocket,

leControleurDesConnexions,listeDesConnexions);

synchronized (listeDesConnexions) {listeDesConnexions.addElement(nouvelleConnexion);

}}catch (IOException e) {

System.err.println (e.getMessage());System.exit (1);

}}

}

ELS - 3 février 2003

Page 204: Programmation Objet Avec Java

POO avec Java - 196 - Etude de cas Une application Client-Serveur

A7: Listing du fichier Serveur: «Connexion.java»//*************************************************************// Connexion.java// E.Lefrançois 8 février 2001// Projet "Chat"//*************************************************************import java.lang.Thread;import java.io.*;import java.net.*;import java.util.*;

//=============================================================// Objet concurrent, responsable de la connexion avec le client// Cet objet a un accès direct à la liste des connexions// maintenue par l'objet "Serveur" (accès en exclusion mutuelle)//=============================================================

class Connexion extends Thread {

// Variables d'instance

private Socket a_socketClient; // socket connectée au client

private Vector a_listeDesConnexions;// Liste des connexions avec les clients

private ControleurConnexions a_leControleurDesConnexions;// Pour contrôler si la connexion// devient inactive

private boolean clientActif; // "true" tant que le client n'a pas// demandé une fermeture de la ssession

private String idClient; // Identificateur du clientprivate BufferedReader canalDentree;

// Canal d'entrée avec le client (texte)

private PrintWriter canalDeSortie;// Canal de sortie avec le client (Texte)

private DataOutputStream outputDataCanal;// Canal de sortie pour données uniquement: non utilisé !!// Pour le créer éventuellement:// outputDataCanal = new DataOutputStream (clientSocket.getOutputStream());

// Constructeurpublic Connexion ( Socket socketClientAssociée,

ControleurConnexions controleurAssocié,Vector listeDesConnexions) {

System.out.println (">> Nouvelle connexion..");a_socketClient = socketClientAssociée;a_leControleurDesConnexions = controleurAssocié;a_listeDesConnexions = listeDesConnexions;clientActif = true;try {

ELS - 3 février 2003

Page 205: Programmation Objet Avec Java

POO avec Java - 197 - Etude de cas Une application Client-Serveur

canalDentree = new BufferedReader(new InputStreamReader(a_socketClient.getInputStream()));

canalDeSortie = new PrintWriter(a_socketClient.getOutputStream());

}catch (IOException e) {

fermerConnexion();return;

}this.start(); // Démarrer l'activité

}

// Activité concurrentepublic void run() {

String ligneDeTexte; // Texte envoyé par le client

// Boucle d'attentetry {

while (clientActif) {ligneDeTexte = canalDentree.readLine();System.out.println (ligneDeTexte);

analyserLigneRecue(ligneDeTexte);}

}catch (IOException e) {}finally {

fermerConnexion();}

}

// Méthodes privéesprivate void analyserLigneRecue (String ligne) {

StringTokenizer st = new StringTokenizer (ligne, "#");try {

String typeInfo = st.nextToken();if (typeInfo.equals("CTR")){

String info = st.nextToken();if (info.equals("LOGIN")) {

this.idClient = st.nextToken();envoyerTexteAuClient ("#CTR#LOGIN_OK");broadcast ("#DATA#*chat*#"

+ "Bienvenue à " + idClient);}else if (info.equals("LOGOUT")) {

broadcast ("#DATA#*chat*#"+ idClient + " quitte le Chat ! bye bye");clientActif = false;

}else erreurDeDonnees(ligne);

}else if (typeInfo.equals("DATA")){

broadcast ("#DATA#"+ idClient + "#" + st.nextToken());

ELS - 3 février 2003

Page 206: Programmation Objet Avec Java

POO avec Java - 198 - Etude de cas Une application Client-Serveur

}else erreurDeDonnees(ligne);

}catch (Exception e) {erreurDeDonnees(ligne);}

}

private void broadcast(String texte) {// Envoyer le message à chaque clientConnexion uneConnexion;synchronized (a_listeDesConnexions) {

for ( Enumeration en =a_listeDesConnexions.elements();en.hasMoreElements();) {

uneConnexion = (Connexion)en.nextElement();synchronized (uneConnexion) {uneConnexion.canalDeSortie.println (texte);uneConnexion.canalDeSortie.flush();

}}

}}

private synchronized void envoyerTexteAuClient(String texte) {

canalDeSortie.println (texte);canalDeSortie.flush();

}

private void erreurDeDonnees (String ligneRecue) {fermerConnexion();

}

private void fermerConnexion() {try {

a_socketClient.close();}catch (IOException e) {}

synchronized (a_leControleurDesConnexions) {a_leControleurDesConnexions.finDeConnexion(this);// Le signaler au contrôleur des connexions

}}

ELS - 3 février 2003

Page 207: Programmation Objet Avec Java

POO avec Java - 199 - Etude de cas Une application Client-Serveur

A8: Listing du fichier Serveur: «ControleurConnexions.java»//*************************************************************// ControleurConnexions.java// E.Lefrançois 1er janvier 2000// Projet "Chat"//*************************************************************import java.lang.Thread;import java.io.*;import java.net.*;import java.util.*;import java.lang.System;

//=============================================================// Objet actif// Cet objet attend le signal "Fin de connexion", envoyé par un// objet de type "Connexion"// En réponse à un tel signal, cet objet enlève la connexion cor// respondante de la liste dynamique des connexions.//// Toutes les 10 secondes, si aucun signal n'est reçu, chaque// connexion est contrôlée, puis détruite si inactive (ce qui ne// devrait pas arriver normalement).// Cet objet a un accès direct à la liste des connexions// maintenue par l'objet de type "Serveur" (liste accédée en// exclusion mutuelle)//=============================================================

class ControleurConnexions extends Thread {

// Variables d'instanceprivate Vector a_listeDesConnexions; // Liste des connexionsprivate static final int DUREE_DE_VEILLE = 10000;

// Périodicité de contrôle (msec)

// Constructeur(s)public ControleurConnexions (Vector liste) {

a_listeDesConnexions = liste;this.start(); // Pour démarrer l'activité

}

// Méthodes publiquespublic void finDeConnexion (Connexion c) {// Fin de la connexion "c"

this.notify();}

// Activitépublic synchronized void run () {

while (true) {try {

this.wait (DUREE_DE_VEILLE);// attend le signal "fin de connexion"// (au travers d'un signal "notify()")// Attend max. 10 sec.this.enleveConnexionsInactives();

}catch (InterruptedException e) {

ELS - 3 février 2003

Page 208: Programmation Objet Avec Java

POO avec Java - 200 - Etude de cas Une application Client-Serveur

// Thread interrompu par un autre Thread (au travers// de la méthode "interrupt" de la classe Thread)// Cet événement ne devrait pas arriver ici.

}}

}

// Méthodes privéesprivate void enleveConnexionsInactives() {

Connexion uneConnexion;synchronized (a_listeDesConnexions) {

for (int cpt = a_listeDesConnexions.size()-1;cpt >= 0; cpt--) {

uneConnexion =(Connexion)a_listeDesConnexions.elementAt(cpt);if (! uneConnexion.isAlive()) {

a_listeDesConnexions.removeElementAt(cpt);System.out.println ("Deconnexion d'un client");

}}

}}

A9: Classe «Socket»

Socket(): création d'une socket non connectée.

Socket( String hote, int port): création d'une socket connectée à l'hôte en paramétre sur le port en paramétre. Peut lever une exception Unk-nownHostException si on ne trouve pas l'hôte, ou une IOException s'il n'y a pas de serveur lancé sur le port.

Socket( InetAddress hote, int port): création d'une socket connec-tée à l'hôte en paramètre sur le port en paramètre. Peut lever une exception IOException s'il n'y a pas de serveur lancé sur le port.

InetAddress getInetAddress(): l'adresse de l'hôte auquel la socket est connectée.

InetAddress getLocalInetAddress(): l'adresse locale de la socket.

int getPort(): le port de l'hôte auquel la socket est connectée.

int getLocalPort(): le port local de la socket.

InputStream getInputStream(): retourne un InputStream pour la soc-ket.

OutputStream getOutputStream(): retourne un OutputStream pour la socket.

synchronized void close(): fermeture de la socket.

String toString(): une chaîne représentant la socket.

ELS - 3 février 2003

Page 209: Programmation Objet Avec Java

POO avec Java - 201 - Etude de cas Une application Client-Serveur

A10: Classe «ServerSocket»

Cette classe implémente un serveur de socket. Un serveur de socket attend l'arrivée d'une requête de connexion, et y répond éventuellement en créant une socket.

ServerSocket(int port): crée un serveur de socket sur le port en paramè-tre. Lève éventuellement une IOException.

ServerSocket(int port, int lq): crée un serveur de socket sur le port en paramètre, et avec une file d'attente de longueur lq (lq est à 50 par défaut). Lève éventuellement une IOException.

ServerSocket(int port, int lq, InetAddress ia): crée un serveur de socket sur le port en paramètre, et avec une file d'attente de longueur lq, et une adresse locale ia. Lève éventuellement une IOException.

Socket accept(): attend une connexion et l'accepte. Lève éventuellement une IOException. La méthode bloque l'exécution du programme jusqu'à ce qu'une connexion soit acceptée.

void close(): ferme la socket. Lève éventuellement une IOException.

InetAddress getInetAddress(): retourne l'adresse locale du serveur de socket.

int getLocalPort(): retourne le port local du serveur de socket.

A11: Classe «InetAddress»

La classe InetAddress représente un nom d'hôte et ses numéros IP.

Byte[] getAddress(): retourne le numéro IP sous forme de tableau d'octets.

String getHostAddress(): retourne le numéro IP sous forme de chaîne de caractères.·static InetAddress getByName(String hote): retourne une Ine-tAddress pour un nom d'hôte. Peut lever une exception UnknownHostExcep-tion.

static InetAddress getLocalHost(): retourne une InetAddress de la machine locale.

ELS - 3 février 2003

Page 210: Programmation Objet Avec Java

POO avec Java - 202 - Etude de cas Une application Client-Serveur

ELS - 3 février 2003

Page 211: Programmation Objet Avec Java

POO avec Java - 203 -

Annexe C UML: Un résumé du dia-gramme de classes

UML est un acronyme pour «Unified Modeling Language».

Il s'agit d'un langage universel, essentiellement graphique, destiné à la modélisation de systèmes informatiques.

Ses auteurs: Grady Booch, James Rumbaugh et Ivar Jacobson (de Rational Software Corporation).

Origine du travail: Unification de la notation à partir des 3 méthodes: Booch, OMT (Rumbaugh) et OOSE (Jacobson). D'autres méthodes ont encore été prises en compte (comme Fusion de Hewlett Packard par exemple).

Version actuelle: 1.3 (Mars 2000)

En l'état, UML n'est qu'une notation: UML ne décrit pas encore de méthode, ou de processus, qui pourraient être observés pour concevoir le modèle.

ELS - 3 février 2003

Page 212: Programmation Objet Avec Java

POO avec Java - 204 -

Concernant la méthode, les auteurs «y pensent» et nous promettent des documents qui viendront dans un futur proche...

Toutefois, les auteurs nous avertissent dors et déjà qu'ils ne présenteront pas les détails d'une méthode de développement général: «un processus de développement devrait être adapté à la culture spécifique de l'entreprise et au domaine d'application».

En attendant, il nous reste les méthodes propres aux 3 auteurs: Booch, OMT et OOSE, ainsi que la méthode Fusion, et d'autres encore...

6.5 LES DIAGRAMMESLes diagrammes interviennent dans les phases d'analyse et de conception du développe-ment informatique. Il s'agit dans les deux cas d'élaborer un modèle: modèle du système réel à informatiser dans le cas de l'analyse, et modèle de l'architecture du logiciel dans la phase de conception.

Un diagramme particulier met en jeux un certain nombre d'éléments du modèle ; et les mêmes éléments peuvent apparaître dans plusieurs diagrammes. Un élément qui inter-vient dans un diagramme particulier n'y sera pas toujours représenté dans tous ses détails: cet élément joue un certain rôle dans le diagramme, et seules les caractéristi-ques de l'élément qui sont propres à ce rôle y sont représentées.

Les diagrammes sont les outils de base de la modélisation. Néanmoins, un bon nombre d'aspects ne pourront être décrits simplement par des diagrammes. Ainsi, il est très fré-quent que les informaticiens fassent usage de descriptions textuelles, de tableaux, etc... pour décrire leurs modèles.

Voici la liste des diagrammes qui constituent le noyau de base du langage UML:

Diagramme de classe (Class diagram)

Diagramme d'étude de cas (Use case diagram)

Diagramme de séquence (Sequence diagram)

Diagramme de collaboration (Collaboration diagram)

Diagramme d'états (State diagram)

Diagrammes de composants (Component diagram)

Diagramme de déploiement (Deployment diagram)

6.6 DIAGRAMMES DE CLASSESLes diagrammes de classe constituent le cœur du langage UML. Ces diagrammes of-frent une vue statique1 du système, en présentant les classes et les relations entre ces

ELS - 3 février 2003

Page 213: Programmation Objet Avec Java

POO avec Java - 205 -

classes.

Deux types de diagrammes appartiennent à cette catégorie:

1. le diagramme des classes à proprement parlé, qui décrit le modèle général du système ;

2. les diagrammes d'objets1 qui décrivent des instanciations particulières du dia-gramme des classes.

6.7 CLASSESUne classe est décrite par un rectangle composé de trois compartiments:

1. Le nom de la classe;2. La liste des attributs;3. La liste des opérations;

Les deux derniers compartiments ne sont pas forcément représentés.

Figure 23: Représentation d'une classe

Nom de la classe

Le nom est écrit en fonte grasse.

La fonte italique est utilisée pour représenter une classe abstraite (modèle géné-ral, sans instanciation).

Le nom peut mentionner le paquetage[Voir paragraphe - Paquetages (packages) - page 216] (groupe) auquel appartient la classe (correspondant au concept de «package» en Java). Le nom de la classes sera dans ce cas pleinement qualifié en obéissant au for-

1. D'autres types de diagrammes sont utilisés pour montrer la vue dynamique du système: diagrammes d'états, scénarios, etc...

1. Un diagramme de classes contient des classes, et un diagramme d'objets contient des objets. Il est possible toutefois, pour différentes raisons, de mélanger classes et objets dans le même diagramme. La répartition « classes ? objets » ne doit pas être rigide.

afficher(on: GraphicsContext)deplacer (delta: Point)effacer()pointInscrit (p: Point): Boolean

centre: PointcouleurFond: ColorcouleurBordure: Colorpoints: List of Point

java.awt::Polygone

ObjetGraphique

afficher(on: GraphicsContext)deplacer (delta: Point)effacer()

Une classe abstraite

Pour indiquer le

chemin d'importation

«Graphics»Polygone

Pour indiquer le

"stéréotype"

ELS - 3 février 2003

Page 214: Programmation Objet Avec Java

POO avec Java - 206 -

mat:

NomDuPaquetage::NomdeLaClasse

Le stéréotype1 de la classe peut être indiqué entre guillemets: «stéréotype», et sera placé au dessus du nom de la classe.Pour accompagner le nom d'une classe, voici quelques stéréotypes possibles: «contrôleur», «acteur», «interface»,...·

D'autre propriétés, comme «ReadOnly», «auteur: Dupond», «Métaclasse».., peuvent encore être indiquées en dessous du nom de la classe, toujours entre guillemets.

AttributsUn attribut est spécifié par un nom, un type, et éventuellement une valeur initiale:nom: Type = valeurInitialeLa valeur initiale est désignée par une expression qui dépend du langage cible. Son évaluation dépend également du langage cible.

☺ Les attributs correspondent aux variables d'instances de types primitifs: int, float, char, boolean,.., et aux objets de type «String».Les variables d'instance de type «Object» ne seront pas considérés comme attributs, mais comme sous-objets. Voir le paragraphe [Associations: Agrégations]

Opérations(Méthodes)Une opération est désignée par un texte obéissant au format suivant:

nom(paramètre: type = valeurParDéfaut,...): TypeDuRésultat

Les parenthèses (paramètres) sont obligatoires.Les valeurs par défaut des paramètres sont optionnelles.Le type du résultat est omis dans le cas d'une procédure.

Variables et méthodes de classePar défaut, les attributs et les opérations représentées dans le diagramme correspondent aux variables d'instance et aux méthodes d'instance.

Le nom des variables de classe et des méthodes de classe (pour des langages comme Smalltalk ou Java) seront précédés du caractère «$» [Voir figure - Portée, visibilité et proprié-tés des attributs et des méthodes - page 207]

1. Pour indiquer une catégorie, une caractéristique...

ELS - 3 février 2003

Page 215: Programmation Objet Avec Java

POO avec Java - 207 -

Visibilité des élémentsIl est possible de spécifier la visibilité externe d'un élément (attribut ou méthode) au moyen d'un caractère qui précède le nom [Voir figure - Portée, visibilité et propriétés des at-tributs et des méthodes - page 207] .

«+» pour un élément public «#» pour un élément protégé (visible uniquement par les sous-classes)«-» pour un élément privé

L'absence d'un tel caractère signifie simplement que la visibilité n'est pas spécifiée.

☺ Dans le cade du cours, nous supposerons que l'absence d'un symbole de visibi-lité signifie:

pour un attribut, qu'il est privé ;pour une méthode, qu'elle est publique.

Autres propriétés des élémentsLes spécificités propres à tel ou tel langage de programmation peuvent être montrées au moyen d'une «propriété» qui suivra la spécification de l'élément (attribut ou méthode).

Encore une fois, de telles propriétés seront écrites sous forme de «stéréotypes»: <<stéréotype>>

Quelques exemples de propriétés: «constructeur», «méthodes privées», «const», «abstract»,...

Figure 24: Portée, visibilité et propriétés des attributs et des méthodes

«Graphics»Window

+$Creer () «abstract»

+ afficher () «abstract»+ cacher() «abstract»

- attacherXWindow(xwin: XWindow)

+$tailleParDéfaut: TailleRectangle = (100, 100)#$tailleMaximum: TailleRectangle = (500, 500)

+ taille: TailleRectangle = tailleParDéfaut

# visibilité: boolean = invisible

- Xwin: XWindow

ELS - 3 février 2003

Page 216: Programmation Objet Avec Java

POO avec Java - 208 -

6.8 CLASSES UTILITAIRESUne classe utilitaire est représentée au moyen d'un rectangle comportant une «ombre» à droite et en bas.

Une classe utilitaire (ou classe «fonctionnelle») est un regroupement d'attributs et d'opérations pouvant en général être utilisés globalement au sein du système.

Figure 25: Une librairie mathématique (classe «utilitaire»)

6.9 OBJETSUn objet est représenté par un rectangle comportant 2 compartiments.

Nom de l'objetLe compartiment du haut contient un texte - souligné - répondant au format:

nomObjet: NomClasse

Le nom de l'objet peut être omis (objet anonyme)Comme pour les classes, le nom de l'objet peut être accompagné de quelques propriétés sous la forme de de «stéréotypes1» : <<stéréotype>>.

Valeur de l'objetLe compartiment du bas contient la liste des attributs avec leur valeur:nomAttribut: Type = valeur

Le compartiment du bas peut être omis (l'objet est uniquement représenté par son nom)·Le type des attributs peut être omis (c'est le cas en général).

Math

$ sin (angle: double): double$ cos (angle: double): double$ random (): double

$ racineAléatoire: long = 0$ pi: double = 3.14159265358979

1. Type, catégorie

ELS - 3 février 2003

Page 217: Programmation Objet Avec Java

POO avec Java - 209 -

Figure 26: Représentation d'un objet

MultiplicitéLa multiplicité d'un objet (nombre d'occurrences) peut être représentée dans un coin de l'hexagone par une valeur entière, un intervalle de valeurs ou encore un astérisque:

*, 3, 7..12, 10..*,..* pour «0» ou plus ,n pour indiquer un nombre exact d'occurrences a..b pour un nombres d'occurrences compris entre «a» et «b»a..* pour un nombre d'occurrences compris entre «a» ou plus

Par défaut, la multiplicité vaut «1».

Pour améliorer la lisibilité du diagramme, un ensemble d'objets peut être représenté visuellement par des hexagones qui se superposent.

6.10MULTIPLICITÉ DES OBJETS, EXEMPLE D'UN FEU DE LA CIRCU-LATION

6.11 ASSOCIATIONSLes associations représentent les relations structurelles qui existent entre objets de dif-

triangle1: Polygone

center: (0, 0)points = ((0,0), (4,0), (4,3))couleurFond = noircouleurBordure = blanc

ContrôleurLampe

CapteurPoids CapteurVisuel

1..3

* *

1

Contrôleur

CapteurVisuel

1

CapteurPoids* *

Lampe1..3

ou

ELS - 3 février 2003

Page 218: Programmation Objet Avec Java

POO avec Java - 210 -

férentes classes.

La plupart des associations sont binaires. Elles sont représentées par une ligne reliant les deux classes.

Une association est caractérisée par:

un nom,un rôle pour chacune des classes qui participent à l'association,unecardinalité (nombre d'objets participant à une association).

Dans la figure [Voir figure - Représentation d'une «association-OU» - page 211], ci-dessous, l'association «Dirige» se lit:

«une personne, en tant que chef, dirige 0 ou plusieurs personne»ou: «une personne, en tant que subordonné, est dirigée par 1 chef au maximum (éventuellement par aucun chef)»

Figure 27: Représentation d'une association binaire

Nom de l'associationLe nom de l'association peut être éventuellement accompagné d'une petite flèche de di-rection (triangle plein) qui précise le sens de lecture.Le nom de l'association peut être omis, et ceci notamment quand les rôles des classes sont spécifiés.

Rôle des classesLe rôle d'une classe, écrit à l'extrémité du lien d'association, décrit la manière dont cette classe est «vue» par l'autre classe.

CardinalitéPour accompagner le rôle de chacune des classes, la cardinalité indique le nombre d'ins-tances pouvant être associées avec une instance de l'autre classe.La cardinalité est indi-

Entreprise

nomadresse

Personne

nomno AVSadresse

Travaille-pour* *

employeur employé

Marrié-à

femme

mari 0..1

0..1

chef

subordonné0..1

* Dirige

ELS - 3 février 2003

Page 219: Programmation Objet Avec Java

POO avec Java - 211 -

quée par une expression:

n pour un entier désignant le nombre exact d'instancesa..b pour un intervalle de valeursa a..* pour un intervalle sans limite supérieure* pour indiquer «plusieurs», équivalent à 0..*par défaut, si rien n’est indiqué, la cardinalité vaut 0..1

Associations multiplesIl est très courant d'avoir plusieurs associations entre deux classes. Elles seront chacune représentée par une ligne et seront tout à fait indépendantes l'une de l'autre.

Association-OUQuand une classe participe simultanément à deux associations, il peut arriver qu'il y ait exclusivité: un objet de cette classe ne pouvant participer qu'à une des deux associations à la fois.

Cette particularité peut être représentée en plaçant une contrainte «or» entre les deux associations, comme le montre la figure suivante.

Figure 28: Représentation d'une «association-OU»

6.11.1 AgrégationsUne agrégation est une forme spéciale d'association. Une agrégation dénote une relation de type «Est-composé-de».

De telles associations se singularisent par le fait que les durées de vie des entités mises en relation sont fortement dépendantes: la durée de vie du «composant» est dépendante de la durée de vie du «composite» qui le contient. Si le composite est détruit, ses com-posants le sont avec.

Dans la plupart des cas, le «composant» est un objet créé par le «composite «lui-même.Le fait de représenter explicitement une agrégation dans un diagramme n'impli-que absolument rien sur son implémentation. Notamment, la référence entre le «com-posant» et le «composite» pourra être bidirectionnelle ou unidirectionnelle.

L'agrégation est représentée par un diamant, dessiné du côté du composite.

CompteBancaire Personne

Détenu-par 1

*

détenteur*Compagnie

Détenu-par

détenteur

or

ELS - 3 février 2003

Page 220: Programmation Objet Avec Java

POO avec Java - 212 -

Figure 29: Représentation de l'agrégation

Cardinalité du compositeDu côté du composite (du côté diamant), la cardinalité vaut «1» par défaut.

Une cardinalité de «1» correspond à une agrégation physique: un composant ne fait partie que d'un seul composite à la fois.

Une cardinalité supérieure à «1» désigne une agrégation de type catalogue: un même composant peut faire partie de plusieurs modèles d'assemblage (plusieurs catalogues présentent le même produit...).

Figure 30: Agrégation représentée par un arbre

6.11.2 Associations ternairesLes associations ternaires (ou d'ordre supérieur) sont représentées par un diamant relié à chacune des classes qui participent à la relation.

Contient1 3..*Point

Window

PaneauBarreTitre BarreScrolling Bordure

BoutonFermeture Titre Fleche Indicateur

11

1 1 12

1

1

1 * 0..2 1

ELS - 3 février 2003

Page 221: Programmation Objet Avec Java

POO avec Java - 213 -

Figure 31: Représentation d'une association ternaire

Les relations ternaires sont rarement utilisées. Elles sont souvent remplacées par une association binaire à laquelle on rattache une classe (cette classe contient alors les élé-ments intéressants de la 3ème classe qui participait à la relation).

Figure 32: Transformation d'une relation ternaire

6.11.3 Propriété de «navigabilité»Une propriété de «navigabilité» peut être attachée à une extrémité d'une association pour pointer sur un objet «cible». Une telle propriété signifie que l'implémentation du logi-ciel doit offrir un moyen direct pour atteindre l'objet cible depuis l'objet situé de l'autre côté de l'association.

En général, à l'implémentation, cette propriété sera mise en œuvre au moyen d'un poin-teur (une simple variable d'instance en Java) ou au moyen d'une table avec fonction de hachage.

Cette propriété est représentée par une flèche.

En l'absence d'un tel symbole, l'association est considérée comme étant bidirection-nelle. La présence d'une propriété de «navigabilité» désigne par conséquent une asso-ciation unidirectionnelle.Les décisions concernant les propriétés de «navigabilité» sont prises en général assez tard, pendant les phases de conception ou d'implémentation.

vol placePlace

date

Personnenom

passager

VolReservation-pour

vol

Reservationdate

Personnenom

noPlace

ELS - 3 février 2003

Page 222: Programmation Objet Avec Java

POO avec Java - 214 -

Figure 33: Représentation de la propriété de «navigabilité»

6.12 HÉRITAGEL'héritage est une relation entre une superclasse et ses sousclasses.

Cette relation est souvent désignée par les termes génénéralisation ou spécialisation.

L'héritage est représenté par une ligne fléchée dont l'extrémité est un triangle vide, pointant sur la superclasse.

Généralisation-OUEn général, les sousclasses représentent différentes alternatives. La figure qui suit mon-tre qu'une «Figure» pourrait être une «Ligne» ou un «Arc», ou encore un «Spline».

Figure 34: Généralisation-OU

Généralisation-ETCe type de généralisation est beaucoup moins fréquent.

Polygone Contient

1

3..*Point

ModeDeRemplissagecouleur

texture

densité

couleur

afficher() «abstract»

Ligneextrémités

afficher()

Arcrayon

angleDépart

angleArc

afficher()

SplinePointsDeContrôle

afficher()

ELS - 3 février 2003

Page 223: Programmation Objet Avec Java

POO avec Java - 215 -

Les sousclasses ont pour fonction de représenter un certain aspect (une certaine «dimension») de la superclasse. Ces dimensions coexistent simultanément et sont com-plètement indépendantes.

Dans ce modèle, la superclasse et ses sousclasses sont toutes des classes abstraites. Les classes concrètes doivent hériter simultanément de toutes les sousclasses, de manière à ce que toutes dimensions soient prises en compte.Un descriminateur est rattaché à l'arc de généralisation. Ce descriminateur représente la dimension mise en œuvre par la sousclasse.

Figure 35: Représentation de la Généralisation-ET

Type de partitionIl est possible d'utiliser des contraintes pour indiquer le type de partition de la superclas-se.

La contrainte {disjoint} indique que les sousclasses n'ont aucun élément en commun.

La contrainte {overlapping} indique au contraire un recouvrement: les sousclasses ne sont pas mutuellement exclusives: cela signifie qu'un objet peut être l'instance de plu-sieurs sousclasses (héritage multiple).

Les contraintes {complete} ou {incomplete} peuvent accompagner la contrainte {dis-joint}.

Véhicule

VéhiculeAVent VéhiculeTerrien

Superclasse à deux dimensions: - mode de propulsion - lieu d'utilisation

VéhiculeAMoteur

propulsionpropulsion

VéhiculeAquatique

lieu lieu

Camion Voilier Classes concrètes

ELS - 3 février 2003

Page 224: Programmation Objet Avec Java

POO avec Java - 216 -

Figure 36: Représenter le type de partition

6.13 PAQUETAGES (PACKAGES)Les applications de taille importante nécessitent un découpage en sous-systèmes qui permettront de gérer l'organisation interne du modèle. La notion de paquetage («packa-ge») a été introduite à cet usage. Un paquetage est un regroupement de classes qui col-laborent pour mettre en œuvre une certaine fonctionnalité.

Les paquetages peuvent être imbriqués. Il est possible ainsi de mettre en place une hié-rarchie de paquetages.

Un paquetage est représenté par deux rectangles superposés. Le premier comprend le nom du paquetage, et le deuxième présente son contenu.

La figure [Voir figure - Les paquetages - page 217] présente 4 paquetages: les paquetages «Clients», «Modèle financier», «Stockage persistant» et «Réseau». Dans cet exemple, on peut voir deux classes à l'intérieur du paquetage «Modèle financier»: «Compte» et «Consommateur »; ainsi qu'un paquetage imbriqué: «Banque».

Pour chacun des paquetages, il est possible d'en présenter le contenu détaillé (s'il ne contient pas trop de classes). Dans le cas d'un diagramme montrant l'architecture glo-bale du système, les contenus des paquetages seront en général omis.

Dans notre exemple, seul le paquetage «Modèle financier» est détaillé, mais à moi-tié seulement: le détail du paquetage imbriqué «Banque» n'est pas présenté.

Quand un paquetage n'est pas détaillé, son nom est marqué dans le deuxième rectangle.

Véhicule

VéhiculeAVent VéhiculeTerrienVéhiculeAMoteur

propulsion{overlapping}

VéhiculeAquatiqueVéhiculeAvent

lieu{overlapping}

CompteBancaire

CompteEntreprise ComptePrivé

{disjoint, complete}

VoitureAmphibie

ELS - 3 février 2003

Page 225: Programmation Objet Avec Java

POO avec Java - 217 -

Le système dans sa globalité est lui-même un paquetage.

Figure 37: Les paquetages

Référence à un autre paquetageUn paquetage peut contenir des références à des classes appartenant à d'autres paqueta-ges. Au cas où les autres paquetages ne sont pas présentés, de telle références doivent être entièrement qualifiées en répondant au format:

NomDuPaquetage::NomDeLaClasse

Dépendances entre paquetagesLa figure 16 montre des liens de dépendances entre paquetages: le paquetage «Clients», par exemple est dépendant directement des paquetages «Réseau» et «Mo-dèle Financier». Cela signifie que certains éléments de «Clients» utilisent des éléments situés à l'intérieur de ces deux paquetages.Ces liens de dépendances sont pré-sentés au moyen d'une ligne fléchée dessinée en point-tillé. La ligne partant du client (paquetage demandeur de service) vers le paquetage fournisseur de service.

Clients

Consommateur

BanqueCompte

Stockage PersistantRéseau

Modèle financier

ELS - 3 février 2003

Page 226: Programmation Objet Avec Java

POO avec Java - 218 -

ELS - 3 février 2003

Page 227: Programmation Objet Avec Java

ELS - 3 février 2003

POO avec Java - 219 -

Annexe D Index

Aabstract 17, 90abstraites

classes 87méthodes 87

accesseurs 8applet 116Applications distribuées 157architecture 98assert 29assertions 29attente passive 101awt 107axiomes 27

Bblocs synchronisés 133boolean 32byte 32

CC++ 86callback 101char 32classe 13

abstraite 87, 90concrète 91finale 85

Client-serveur 157coercition de type 32concurrence 113

constantes symboliques 23constructeur

de classe 41par défaut 73

constructeurs 8enchaînement 71invocation 15par défaut 73

Contrôleur 103copie

profonde 36superficielle 36

copie d’objets 36CORBA 157cycle de vie 6

Ddesign patterns 97destructeurs 15diagramme de colaboration 47diagramme de séquence 47double 32

Eedition de liens dynamique 60encapsulation 17, 48, 65enchaînement des constructeurs 71envoi de message 5espace de noms 48est-un 77

Page 228: Programmation Objet Avec Java

ELS - 3 février 2003

POO avec Java - 220 -

exceptionsredéfinition 84

exclusion mutuelle 129

Ffinal 17

classes 85méthodes 84

finalize 15float 32fonctions 11

Ggarbage collector 15, 34, 115généralisation-spécialisation 74généricité 86getters 70

Hhéritage 48, 53, 54

d’une méthode de classe 95d’une variable de classe 94multiple 21, 54simple 54

Iimport 49importation 49InetAdress 161int 32interface 11, 55, 92interrogateurs 8interrupt() 149interrupted() 149isAlive() 121isDaemon() 150isInterrupted() 150

Jjoin() 121

Llong 32

Mmain() 50, 116

messageenvoyer 5

méthoded’instance 39de classe 39

méthodesabstraites 87atomiques 131de classe et héritage 95finales 84synchronisées 133

modèle 993-tiers 157Client-serveur 157Composite 104MVC 98Observateur 104

modèles de conception 97Modele-Vue-Contrôleur 98modes de protection 64

package 68par défaut 68privé 65public 65, 66

modificateurs 8moniteurs 129

Nnotify() 135notifyAll() 135

Oobjet 4

actif 122comportement 4, 6création 15cycle de vie 6destruction 15état 4, 6identité 4, 6passif 121propriétés 70

Observable 102Observateur 101observateurs 8

Page 229: Programmation Objet Avec Java

ELS - 3 février 2003

POO avec Java - 221 -

Ppackage 48, 50paquetage 48parallélisme 113partage d’objets 36polymorphisme 53, 85port 160postconditions 25préconditions 24private 65procédures 10processus 110programmation objet 46programmation par contrat 28programmation procédurale 45programme

chat 165protected 67protection

modes de 64public 16, 66

Rramasse-miettes 15, 34redéfinition 66, 80

des exceptions 84du mode de protection 84

règle de substitution 79resume() 148réutilisation 87réutilisation de code 54, 59RMI 157run() 112

Sscheduling 124section critique 131Sémantique des opérations 24ServerSocket 161setDaemon() 150setters 70short 32sleep(...) 147Smalltalk 86Socket 160sockets 154

sousclasse 57sous-typage 78spécialisation 74start() 117static 39, 41stop() 147structure de données 7substitution

règle de 79super 71

utilisation du mot-clé super 93superclasse 57suspend () 148synchronisation

méthodes statiques 134synchronized 131

TTCP 158threads 110

attente sur un événement 135blocage d’un thread 119compétition et coopération

135état Actif 117état Construit 117état Inactif 119exclusion mutuelle 129groupes de threads 142mort d’un thread 120ordonnancement 125portabilité 129préemption 126priorité 124récupération par le garbage

collector 120rendez-vous 135threads démons 142time slicing 126

transtypage 32typage

multiple 21, 55type 78

abstrait 7courant 18, 61de déclaration 61

Page 230: Programmation Objet Avec Java

ELS - 3 février 2003

POO avec Java - 222 -

déclaré 18objet 34primitif 32

UUDP 158UML 11

Vvariable 5, 32

d’instance 39de classe 39de classe et héritage 94volatile 141

Vector 86verrous d’exclusion mutuelle 131volatile 141Vue 99

Wwait() 135wait(..) 150wrappers 33

Yyield() 127