Vous êtes sur la page 1sur 161

LES LANGAGES OBJETS

Principes de base, techniques de programmation

Michel Beaudouin-Lafon
Armand Colin 1992

Table des matires


Chapitre 1 - INTRODUCTION ..............................................1 1.1 Le champ des langages...............................................1 1.2 Historique...................................................................8 1.3 Plan du livre .............................................................12 Chapitre 2 - PRINCIPES DE BASE......................................13 2.1 Classes et instances....................................................14 2.2 Mthodes et envoi de message ..................................17 2.3 Lhritage ................................................................18 2.4 Lhritage multiple...................................................22 2.5 Le polymorphisme ...................................................24 2.6 Les mtaclasses.........................................................28 Chapitre 3 - LANGAGES OBJETS TYPS.......................31 3.1 Classes, objets, mthodes...........................................33 3.2 Hritage....................................................................39 3.3 Hritage multiple......................................................44

3.4 Liaison dynamique...................................................48 3.5 Rgles de visibilit ....................................................52 3.6 Mcanismes spcifiques............................................56 3.7 Conclusion ...............................................................60 Chapitre 4 - SMALLTALK ET SES DRIVS....................61 4.1 Tout est objet............................................................64 4.2 Classes, instances, messages.......................................65 4.3 Hritage....................................................................69 4.4 Les structures de contrle .........................................74 4.5 Mtaclasses...............................................................81 4.6 Les drivs de Smalltalk............................................87 4.7 Conclusion ...............................................................92 Chapitre 5 - PROTOTYPES ET ACTEURS..........................93 5.1 Langages de prototypes............................................93 5.2 Langages dacteurs.................................................104 Chapitre 6 - PROGRAMMER AVEC DES OBJETS ...........117 6.1 Identifier les classes ................................................118 6.2 Dfinir les mthodes...............................................125 6.3 Rutiliser les classes ................................................132 6.4 Exemple : les Tours de Hanoi.................................136 6.5 Conclusion .............................................................140 Bibliographie.....................................................................141 Index.................................................................................145

Avant-propos
Ce livre a pour objectif de prsenter les langages objets et la programmation par objets d'une manire gnrale et nanmoins prcise. Le nombre de langages objets existant aujourd'hui et leur diversit interdisent une tude exhaustive dans un ouvrage de cette taille. C'est pourquoi l'on s'est attach identifier un nombre rduit de concepts de base, partags par de nombreux langages, et illustrer ces concepts par des exemples concrets. Il ne s'agit donc pas d'apprendre programmer avec un langage objets (d'autres ouvrages, spcialiss, s'y emploient), mais de matriser les principes de ces langages et les techniques de la programmation par objets. Ce livre s'adresse des lecteurs ayant une exprience de la programmation avec des langages classiques comme Pascal et Lisp, ou tout au moins une connaissance des principes de ces langages. Il s'adresse donc tout particulirement des tudiants de second et troisime cycle d'Informatique, ou d'autres disciplines dans lesquelles la formation l'Informatique aborde les langages de programmation volus. Ce livre s'adresse galement aux lves des coles d'ingnieurs, aux chercheurs, aux enseignants, et plus gnralement tous ceux qui veulent comprendre les langages objets.

Plusieurs annes d'enseignement des langages objets au D.E.S.S. Systme et Communication Homme-Machine de l'Universit de Paris-Sud, et des confrences au Certificat C4 d'Informatique Applique de cette mme Universit, m'ont conduit la prsentation des langages objets adopte dans ce livre : dans les deux cas, le faible volume horaire interdit tout apprentissage d'un langage particulier et invite une prsentation synthtique. Il en rsulte une grille d'analyse des langages objets, largement maille d'exemples, dont l'ambition est de permettre au lecteur d'aborder la programmation avec un langage objets avec une vision claire et saine de l'univers de ces langages. Plusieurs personnes ont contribu rendre ce livre plus clair et, je l'espre, facile d'accs : Thomas Baudel, Jean Chassain, Stphane Chatty, Marc Durocher, Solange Karsenty ont relu des versions prliminaires de cet ouvrage et apport des commentaires constructifs ; les membres du groupe Interfaces HommeMachine du Laboratoire de Recherche en Informatique, par leur exprience quotidienne de la programmation par objets, ont permis tout la fois de mettre en vidence les ralits pratiques de l'utilisation des langages objets et de mettre l'preuve un certain nombre d'ides prsentes dans ce livre. Marie-Claude Gaudel a galement contribu clarifier les notions lies au typage dans les langages de programmation en gnral et dans les langages objets en particulier. Enfin, mon frre Emmanuel a ralis l'ensemble des figures de cet ouvrage, et je lui en suis infiniment reconnaissant.

Chapitre 1 INTRODUCTION
Les langages objets sont apparus depuis quelques annes comme un nouveau mode de programmation. Pourtant la programmation par objets date du milieu des annes 60 avec Simula, un langage cr par Ole Dahl et Kristen Nygaard en Norvge et destin programmer des simulations de processus physiques. Bien que de nombreux travaux de recherche aient eu lieu depuis cette poque, lessor des langages objets est beaucoup plus rcent. Ce livre essaie de montrer que les langages objets ne sont pas une mode passagre, et que la programmation par objets est une approche gnrale de la programmation qui offre de nombreux avantages.

1.1 LE CHAMP DES LANGAGES


Situer les langages objets dans le vaste champ des langages de programmation nest pas chose facile tant le terme dobjet recouvre de concepts diffrents selon les contextes. Nous prsentons ici plusieurs classifications des langages et situons les langages objets dans ces diffrentes dimensions.

Les langages objets

Classification selon le mode de programmation Cette classification prsente les principaux modles de programmation qui sont utiliss aujourdhui, cest--dire les principaux modles par lesquels on peut exprimer un calcul. La programmation imprative, la plus ancienne, correspond aux langages dans lesquels lalgorithme de calcul est dcrit explicitement, laide dinstructions telles que laffectation, le test, les branchements, etc. Pour sexcuter, cet algorithme ncessite des donnes, stockes dans des variables auxquelles le programme accde et quil peut modifier. La formule de Niklaus Wirth dcrit parfaitement cette catgorie de langages : programme = algorithme + structure de donnes. On classe dans cette catgorie les langages dassemblage, Fortran, Algol, Pascal, C, Ada. Cette catgorie de langages est la plus ancienne car elle correspond naturellement au modle darchitecture des machines qui est la base des ordinateurs : le modle de Von Neumann. La programmation fonctionnelle adopte une approche beaucoup plus mathmatique de la programmation. Fonde sur des travaux assez anciens sur le lambda-calcul et popularise par le langage Lisp, la programmation fonctionnelle supprime la notion de variable, et dcrit un calcul par une fonction qui sapplique sur des donnes dentres et fournit comme rsultat les donnes en sortie. De fait, ce type de programmation est plus abstrait car il faut dcrire lalgorithme indpendamment des donnes. Il existe peu de langages purement fonctionnels ; beaucoup introduisent la notion de variable pour des raisons le plus souvent pratiques, car labstraction complte des donnes conduit souvent des programmes lourds crire. Parmi les langages fonctionnels, il faut citer bien sr Lisp et ses multiples incarnations (CommonLisp, Scheme, Le_Lisp, etc.), ainsi que ML. La programmation logique, ne dune approche galement lie aux mathmatiques, la logique formelle, est fonde sur la

Introduction

description dun programme sous forme de prdicats. Ces prdicats sont des rgles qui rgissent le problme dcrit ; lexcution du programme, grce linfrence logique, permet de dduire de nouvelles formules, ou de dterminer si une formule donne est vraie ou fausse partir des prdicats donns. Linfrence logique est tout fait similaire aux principes qui rgissent la dmonstration dun thorme mathmatique laide daxiomes et de thormes connus. Le plus clbre des langages logiques est Prolog. La programmation par objets est-elle une nouvelle catgorie dans cette classification ? Il est difficile de rpondre cette question. La programmation par objets est proche de la programmation imprative : en effet, l o la programmation imprative met laccent sur la partie algorithmique de la formule de Wirth, la programmation par objets met laccent sur la partie structure de donnes. Nanmoins, ceci nest pas suffisant pour inclure la programmation par objets dans la programmation imprative, car lapproche des langages objets sapplique aussi bien au modle fonctionnel ou logique quau modle impratif. Classification selon le mode de calcul Cette classification complte la prcdente en distinguant deux modles dexcution dun calcul : Les langages squentiels correspondent une excution squentielle de leurs instructions selon un ordre que lon peut dduire du programme. Ces langages sont aujourdhui les plus rpandus, car ils correspondent larchitecture classique des ordinateurs dans lesquels on a une seule unit de traitement (modle de Von Neumann). Les langages objets sont pour la plupart squentiels. Les langages parallles permettent au contraire plusieurs instructions de sexcuter simultanment dans un programme. Lessor de la programmation parallle est d la disponibilit de machines architecture parallle. La programmation

Les langages objets

parallle ncessite des langages spcialiss car les langages usuels ne fournissent pas les primitives de communication et de synchronisation indispensables. Or il savre que le modle gnral de la programmation par objets est facilement paralllisable. Une classe de langages objets, les langages dacteurs, sont effectivement des langages parallles. Classification selon le typage Cette classification considre la notion de type qui, dans les langages de programmation, est destine apporter une scurit au programmeur : en associant un type chaque expression dun programme, on peut dterminer par une analyse statique, cest--dire en observant le texte du programme sans lexcuter, si le programme est correct du point de vue du systme de types. Cela permet dassurer que le programme ne provoquera pas derreur lexcution en essayant, par exemple, dajouter un boolen un entier. Dans un langage typage statique, on associe, par une analyse statique, un type chaque expression du programme. Cest le systme de types le plus sr, mais aussi le plus contraignant. Pascal est lexemple typique dun langage typage statique. Dans un langage fortement typ, lanalyse statique permet de vrifier que lexcution du programme ne provoquera pas derreur de type, sans pour autant tre capable dassocier un type chaque expression. Ceci signifie que les types devront ventuellement tre calculs lexcution pour contrler celle-ci. Les langages qui offrent le polymorphisme paramtrique (ou gnricit), comme ADA, sont fortement typs. La plupart des langages objets typs sont fortement typs, et notamment Simula, Modula3 et C++. Dans un langage faiblement typ, on ne peut assurer, par la seule analyse statique, que lexcution dun programme ne provoquera pas derreur lie au systme de types. Il est donc ncessaire de calculer et de contrler les types lexcution,

Introduction

ce qui justifie lappellation de langage typage dynamique. Parmi les langages objets, Eiffel est faiblement typ car une partie du contrle de types doit tre ralise lexcution. Dans un langage non typ, la notion de type nexiste pas, et il ne peut donc y avoir de contrle sur la validit du programme vis--vis des types. Ainsi, Lisp est un langage non typ, de mme que le langage objets Smalltalk. La notion de type apporte une scurit de programmation indispensable dans la ralisation de gros systmes. De plus, comme nous allons le voir, elle permet de rendre lexcution des programmes plus efficace. Cest donc une notion importante, et lon peut constater que les langages objets couvrent une grande partie de la classification ci-dessus. Cest ce critre qui va nous servir structurer la suite de ce livre en distinguant dun ct les langages typs (fortement ou faiblement), dautre part les langages non typs. De nombreux travaux sont consacrs actuellement ltude des systmes de types dans les langages objets car le concept dhritage, qui est lun des fondements des langages objets, ncessite des systmes de types qui nentrent pas dans les cadres tudis jusqu prsent. Classification selon le mode dexcution Cette classification porte sur la faon dont lexcution du programme est ralise. Le mode dexcution nest pas proprement parler une caractristique dun langage, mais une caractristique de limplmentation dun langage. Les langages interprts permettent lutilisateur dentrer des expressions du langage et de les faire excuter immdiatement. Cette approche offre lavantage de pouvoir tester et modifier rapidement un programme au dtriment de la vitesse dexcution. Outre la vitesse dexcution, les langages interprts ont gnralement pour inconvnient dtre moins srs, car de nombreux contrles smantiques sont raliss au fur et mesure de linterprtation et non dans

Les langages objets

une phase prliminaire de compilation. Beaucoup de langages objets sont interprts, et tirent avantage de cette apparente faiblesse pour donner une plus grande souplesse lexcution des programmes. Ainsi, une modification ponctuelle dun programme peut avoir des effets (contrls) sur lensemble de lexcution, ce qui permet notamment de construire des environnements sophistiqus pour la mise au point des programmes. Les langages compils ncessitent une phase de compilation avant de passer lexcution proprement dite dun programme. Le but de la compilation est essentiellement de produire du code directement excutable par la machine, donc plus efficace. Pour cela, le compilateur utilise en gnral les informations qui lui sont fournies par le systme de types du langage. Cest ainsi que la plupart des langages typs sont compils. De manire gnrale, les langages interprts sont plus nombreux que les langages compils car la ralisation dun compilateur est une tche complexe, surtout si lon veut engendrer du code machine efficace. Les langages objets nchappent pas cette rgle, et il existe peu de langages objets compils. Les langages semi-compils ont les caractristiques des langages interprts mais, pour une meilleure efficacit, ils utilisent un compilateur de manire invisible pour lutilisateur. Ce compilateur traduit tout ou partie du programme dans un code intermdiaire ou directement en langage machine. Si le compilateur engendre un code intermdiaire, cest un interprteur de ce code qui ralisera lexcution du programme. Les langages objets considrs comme interprts sont souvent semi-compils, comme Smalltalk ou le langage de prototypes Self. Classification selon la modularit Cette dernire classification analyse la faon dont les langages permettent au programmeur de raliser des modules et dencapsuler les donnes. La modularit et lencapsulation

Introduction

assurent une plus grande scurit de programmation et fournissent la base de la rutilisation. Absence de modularit : le programmeur doit, par des conventions de programmation et de nommage, prendre en charge la modularit. Cest notamment le cas de Pascal qui oblige donner des noms diffrents toutes les variables, procdures et fonctions globales, et ne permet pas de cacher des dfinitions autrement que par les rgles de visibilit (procdures imbriques), ce qui est insuffisant. Le langage C offre une modularit selon le dcoupage du programme en fichiers, en permettant de dclarer des variables ou fonctions prives un fichier. En revanche, il noffre pas la possibilit dimbrication des procdures et fonctions. Modularit explicite : certains langages offrent la notion de module comme concept du langage, que lon peut composer avec dautres notions. Par exemple, en Ada, la notion de package permet dimplmenter un module, et se combine avec la gnricit pour autoriser la dfinition de modules gnriques (paramtrs par des types). Modularit implicite : les langages objets fournissent une modularit que lon peut qualifier dimplicite dans la mesure o celle-ci ne fait pas appel des structures particulires du langage. Au contraire, la notion de module est implicite et indissociable de celle de classe. La programmation par objets est une forme de programmation modulaire dans laquelle lunit de modularit est fortement lie aux structures de donnes du langage. Par comparaison, le langage Ada offre une modularit beaucoup plus indpendante des structures de donnes et de traitement du langage. La modularit est souvent considre comme un atout important pour la rutilisation des programmes. De ce point de vue, les langages objets permettent une rutilisation bien plus importante que la plupart des autres langages.

Les langages objets

La programmation par objets comme un style De ce tour dhorizon, il faut conclure que la programmation par objets est plus une approche gnrale de la programmation quun type facilement classable. De fait, on voit aujourdhui de nombreuses extensions objets des langages existant : Pascal Objet, Lisp Objet, Cobol Objet, etc. Si certaines de ces extensions ne sont pas toujours un succs, il est un fait que les principes de la programmation par objets sont applicables dans un grand nombre de contextes.

1.2 HISTORIQUE
La figure 1 prsente la gnalogie des principaux langages objets. On y distingue deux ples autour des langages Simula et Smalltalk. La famille Simula Le langage Simula est considr comme le prcurseur des langages objets. Dvelopp dans les annes 60 pour traiter des problmes de simulation de processus physiques (do son nom), Simula a introduit la notion de classe et dobjet, la notion de mthode et de mthode virtuelle. Ces concepts restent la base des langages objets typs et compils, comme on le verra en dtail dans le chapitre 3. Simula a t cr dans la ligne du langage Algol, langage typ de lpoque dont Pascal sest galement inspir. Les langages de la famille Simula sont des langages impratifs typs et gnralement compils. Lintrt dun systme de types est de permettre un plus grand nombre de contrles smantiques lors de la compilation, et dviter ainsi des erreurs qui ne se manifesteraient qu lexcution. Typage et compilation sont deux concepts indpendants : on peut imaginer des langages typs interprts et des langages non typs compils. Cest nanmoins rarement le cas, car les informations dduites du

Introduction

1960

Algol Simula

Lisp

1970

Pascal

Smalltalk

Planner Plasma

1980 Modula2

1990

Flavors ACT1 C++ Objective-C ObjVlisp Object Pascal Beta Eiffel ABCL /1 Self CLOS Modula3 ACT2

Figure 1 - Gnalogie des principaux langages objets typage permettent de gnrer du code plus efficace, ce qui encourage compiler les langages typs. Simula a inspir nombre de langages objets. Certains dentre eux sont maintenant largement plus connus et rpandus que Simula, comme C++ et Eiffel. Plusieurs langages ont t conus en ajoutant des concepts des langages objets un langage existant. Ainsi, Classcal et son descendant Object Pascal ajoutent des classes au langage Pascal, Modula3 est une rvision majeure de Modula2 incluant des objets, C++ est construit partir de C. Une telle approche prsente avantages et inconvnients : il est commode de prendre un langage existant aussi bien pour les concepteurs que pour les utilisateurs car cela vite de tout rinventer ou de tout rapprendre. Dun autre ct, certains aspects du langage de base peuvent se rvler nfastes pour ladjonction de mcanismes des langages objets. La famille Smalltalk Dans les annes 70, sous limpulsion dAlan Kay, ont commenc aux laboratoires Xerox PARC des recherches qui ont conduit au langage Smalltalk, considr par beaucoup comme le

10

Les langages objets

prototype des langages objets. De fait, cest Smalltalk plutt que Simula qui est lorigine de la vague des langages objets depuis les annes 80. Smalltalk sinspire de Simula pour les concepts de classes, dobjets et de mthodes, mais adopte une approche influence par Lisp pour ce qui concerne limplmentation : Smalltalk est un langage semi-compil dans lequel tout est dcid lors de lexcution. Comme dans Lisp, la souplesse due laspect interprt de lexcution est largement exploite par le langage. Dun point de vue conceptuel, Smalltalk introduit la notion de mtaclasse qui nest pas prsente dans Simula. Cette notion permet de donner une description mta-circulaire du langage de telle sorte que lon peut raliser assez simplement un interprteur de Smalltalk en Smalltalk. Cet aspect mtacirculaire est galement prsent dans Lisp, et dans de nombreux langages interprts. Dans le cas de Smalltalk, les mtaclasses sont accessibles lutilisateur de faon naturelle, et permettent de nombreuses facilits de programmation. Lintroduction du modle mta-circulaire dans Smalltalk date de la premire version du langage (Smalltalk-72). Les versions suivantes (Smalltalk-76 et Smalltalk-80) ont affin, amlior et enrichi ce modle. Dautres langages inspirs de Smalltalk sont alls plus loin dans cette direction, notamment ObjVLisp et Self. La meilleure preuve de la puissance de Smalltalk est lensemble des programmes raliss en Smalltalk, et lintense activit de recherche autour du langage, de ses concepts ou de ses langages drivs. Parmi les ralisations, il faut noter lenvironnement de programmation Smalltalk, ralis Xerox PARC et implment lui-mme en Smalltalk. Cet environnement, qui inclut un systme dexploitation, est le premier environnement graphique a avoir t largement diffus. Smalltalk a inspir de nombreux langages de programmation. Alors que Smalltalk est un langage natif, la plupart des langages quil a inspirs sont raliss au-dessus de Lisp. Il savre que Lisp fournit une base au-dessus de laquelle il est facile de

Introduction

11

construire des mcanismes dobjets. On peut nanmoins regretter que dans nombre de cas le langage sous-jacent reste accessible, ce qui donne lutilisateur deux modes de programmation largement incompatibles : le mode fonctionnel de Lisp et le mode de programmation par objets. Certaines incarnations de Lisp intgrent les notions dobjets de faon presque native, comme Le_Lisp et CLOS (CommonLisp Object System). Autres familles, autres langages Si les familles Simula et Smalltalk reprsentent une grande partie des langages objets, il existe dautres langages inclassables, dautres familles en cours de formation. Ainsi le langage Objective-C est un langage hybride qui intgre le langage C avec un langage objets de type Smalltalk. Plutt quune intgration, on peut parler dune coexistence entre ces deux aspects, car on passe explicitement dun univers lautre par des dlimiteurs syntaxiques. Les entits dun univers peuvent tre transmises lautre, mais elles sont alors des objets opaques que lon ne peut manipuler. Lintrt de cette approche est de donner lutilisateur un environnement compil et typ (composante C) et un environnement interprt non typ (composante Smalltalk). Parmi les logiciels raliss en Objective-C, le plus connu est certainement Interface Builder, lenvironnement de construction dapplications interactives de la machine NeXT. Une autre famille est constitue par les langages de prototypes. Ces langages, au contraire des langages objets traditionnels, ne sont pas fonds sur les notions de classes et dobjets, mais uniquement sur la notion de prototype. Un prototype peut avoir les caractristiques dune classe, ou dun objet, ou bien des caractristiques intermdiaires entre les deux. Dune certaine faon, ces langages poussent le concept des objets dans ses derniers retranchements et permettent dexplorer de nouveaux paradigmes de programmation.

12

Les langages objets

Une dernire famille est constitue de langages parallles appels langages dacteurs. Dans un programme crit avec un tel langage, chaque objet, appel acteur, est un processus qui sexcute de faon autonome, mettant et recevant des messages dautres acteurs. Lorsquun acteur envoie un message un autre acteur, il peut continuer son activit, sans se soucier de ce quil advient du message. Le paralllisme est donc introduit par une simple modification du mode de communication entre les objets. Compar aux modles de paralllisme mis en uvre dans dautres langages, comme les tches de Ada, la simplicit et llgance du modle des acteurs est surprenante. Cela fait des langages dacteurs un moyen privilgi dexploration du paralllisme.

1.3 PLAN DU LIVRE


Ce livre a pour but de prsenter les principes fondamentaux des langages objets et les principales techniques de programmation par objets. Il ne prtend pas apprendre programmer avec tel ou tel langage objets. cet effet, les exemples sont donns dans un pseudo-langage la syntaxe proche de Pascal. Ceci a pour but une prsentation plus homogne des diffrents concepts. Le prochain chapitre prsente les principes gnraux qui sont la base de la programmation par objets. Les trois chapitres suivants prsentent les grandes familles de langages objets : les langages objets typs, cest--dire les langages de la famille de Simula (chapitre 3) ; les langages objets non typs, et en particulier Smalltalk (chapitre 4) ; les langages de prototypes et les langages dacteurs (chapitre 5). Le dernier chapitre prsente un certain nombre de techniques usuelles de la programmation par objets et une bauche de mthodologie. Une connaissance des principes de base des langages de programmation en gnral et de Pascal en particulier est suppose dans lensemble de louvrage. Les chapitres 3 et 4

Introduction

13

sont indpendants. Les lecteurs qui prfrent Lisp Pascal peuvent lire le chapitre 4 avant le chapitre 3.

Chapitre 2 PRINCIPES DE BASE


Un langage objets utilise les notions de classe et dinstance, que lon peut comparer aux notions de type et de variable dun langage tel que Pascal. La classe dcrit les caractristiques communes toutes ses instances, sous une forme similaire un type enregistrement ( record Pascal). Une classe dfinit donc un ensemble de champs. De plus, une classe dcrit un ensemble de mthodes, qui sont les oprations ralisables sur les instances de cette classe. Ainsi une classe est une entit autonome. Au lieu dappliquer des procdures ou fonctions globales des variables, on invoque les mthodes des instances. Cette invocation est souvent appele envoi de message. De fait, on peut considrer que lon envoie un message une instance pour quelle effectue une opration, cest--dire pour quelle dtermine la mthode invoquer. On utilise souvent le terme dobjet la place dinstance. Le terme instance insiste sur lappartenance une classe : on parle dune instance dune classe donne. Le terme objet rfre de faon gnrale une entit du programme (qui peut tre une instance, mais aussi un champ, une classe, etc.).

16

Les langages objets

Lien d'hritage Classe de Base Lien d'instanciation Classe Champs Mthodes Envoi de message Objet

Figure 2 - Illustration des notions de base Lhritage est la dernire notion de base des langages objets : une classe peut tre construite partir dune classe existante, dont elle tend ou modifie la description. Ce mcanisme de structuration est fondamental dans les langages objets ; il est dcrit dans la section 2.3 de ce chapitre. La figure 2 dcrit ces quatre notions de base, et introduit les conventions que nous utiliserons dans les autres figures.

2.1 CLASSES ET INSTANCES


La notion dobjet ou instance recouvre toute entit dun programme crit dans un langage objets qui stocke un tat et rpond un ensemble de messages. Cette notion est comparer avec la notion usuelle de variable : une variable stocke un tat, mais na pas la capacit par elle-mme deffectuer des traitements. On utilise pour cela dans les langages classiques des sous-programmes, fonctions ou procdures. Ceux-ci prennent des variables en paramtre, peuvent les modifier et peuvent retourner des valeurs.

Principes de base

17

Dans un langage classique, on dfinirait par exemple un type Pile et des procdures pour accder une pile ou la modifier : Empiler, qui prend une pile et une valeur empiler ; Dpiler, qui prend une pile ; Sommet, qui prend une pile et retourne une valeur. Cette sparation entre variables et procdures dans les langages classiques est la source de nombreux problmes en ce qui concerne lencapsulation : pour des raisons de scurit de programmation, on ne souhaite pas que nimporte quelle procdure puisse accder au contenu de nimporte quelle variable. On est alors amen introduire la notion de module. Un module exporte des types, des variables et des procdures. De lextrieur, les types sont opaques : leur implmentation nest pas accessible. On ne peut donc manipuler les variables de ce type que par lintermdiaire des procdures exportes par le module. lintrieur du module, limplmentation des types est dcrite, et est utilisable par les corps des procdures. Ainsi, on peut encapsuler la notion de pile dans un module. Ce module exporte un type Pile, et les procdures Empiler, Dpiler et Sommet, dont limplmentation nest pas connue de lextrieur. Par cette technique, un utilisateur du module ne pourra pas modifier une pile autrement que par les procdures fournies par le module, ce qui assure lintgrit dune pile. Le module constitue donc le moyen de regrouper types et procdures, pour construire des types abstraits. Les langages Clu, Ada et ML, parmi dautres, offrent de telles possibilits. Dans les langages qui noffrent pas de modularit (Pascal, C, Lisp etc.), le programmeur doit faire preuve dune grande rigueur pour reproduire artificiellement, cest--dire sans aide du langage, lquivalent de modules. Lapproche des langages objets consiste intgrer demble la notion de variable et de procdures associes dans la notion dobjet. Lencapsulation est donc fournie sans mcanisme additionnel. De mme quune variable appartient un type dans

18

Les langages objets

Pile pile sommet Empiler Dpiler Sommet

pile sommet

Figure 3 - La classe Pile et une instance un langage classique, dans un langage objets un objet appartient une classe. La classe est la fois un type et un module : elle contient une description de type, sous forme de champs, ainsi quun ensemble de procdures associes ce type, appeles mthodes. Dfinir une classe On dfinira ainsi une classe Pile par : un tat reprsentant la pile (tableau, liste, etc.), constitu de champs. Pour une reprsentation par tableau, on aura ainsi deux champs : le tableau lui-mme et lindice du sommet courant. la mthode Empiler, qui prend une valeur en paramtre. la mthode Dpiler. la mthode Sommet, qui retourne une valeur. On cre des objets partir dune classe par le mcanisme dinstanciation. Le rsultat de linstanciation dune classe est un objet, appel instance de la classe (voir figure 3). Linstanciation est similaire la cration dune variable dun

Principes de base

19

type enregistrement. Linstance cre stocke un tat constitu dune valeur pour chacun de ses champs. Les champs sont euxmmes des objets. Un certain nombre de classes sont prdfinies dans les langages, telles que la classe des entiers, des caractres, etc. Lencapsulation, cest--dire laccs contrl aux objets, est assure naturellement par les classes. Bien que diffrant dun langage lautre, comme nous le verrons, on peut considrer dans un premier temps que les champs sont privs alors que les mthodes sont publiques. Ceci signifie que les champs sont visibles uniquement depuis le corps des mthodes de la classe, alors que les mthodes sont visibles de lextrieur. Nous allons maintenant dcrire le mcanisme dinvocation des mthodes.

2.2 MTHODES ET ENVOI DE MESSAGE


Dans les langages objets, les objets stockent des valeurs, et les mthodes permettent de manipuler les objets. Ceci est comparable aux langages classiques, dans lesquels les variables stockent des valeurs et les procdures et fonctions permettent de manipuler les variables. Mais, contrairement aux procdures et fonctions qui sont des entits globales du programme, les mthodes appartiennent aux classes des objets. Au lieu dappeler une procdure ou fonction globale, on invoque une mthode dun objet. Lexcution de la mthode est alors ralise dans le contexte de cet objet. Linvocation dune mthode est souvent appele envoi de message. On peut en effet considrer que lon envoie un objet, par exemple une pile, un message, par exemple empiler la valeur 20 . Dans un langage classique, on appellerait la procdure Empiler avec comme paramtres la pile elle-mme et la valeur 20. Cette distinction est fondamentale. En effet, lenvoi de message implique que cest le receveur (ici la pile) qui dcide comment empiler la valeur 20, grce la mthode dtenue par

20

Les langages objets

sa classe. Au contraire, lappel de procdure des langages classiques implique que cest la procdure (ici Empiler) qui dcide quoi faire de ses arguments, dans ce cas la pile et la valeur 20. En dautres termes, la programmation imprative ou fonctionnelle privilgie le contrle (procdures et fonctions) alors que la programmation par objets privilgie les donnes (les objets), et dcentralise le contrle dans les objets. Le corps dune mthode est excut dans le contexte de lobjet receveur du message. On a donc directement et naturellement accs aux champs et mthodes de lobjet receveur. Cest en fait seulement dans le corps des mthodes que lon a accs aux parties prives de lobjet, cest--dire en gnral ses champs (les langages diffrent sur la dfinition des parties prives). La dfinition conjointe des champs et des mthodes dans les classes est la base du mcanisme dhritage, que nous allons maintenant dcrire.

2.3 LHRITAGE
La notion dhritage est propre aux langages objets. Elle permet la dfinition de nouvelles classes partir de classes existantes. Supposons que lon veuille programmer le jeu des Tours de Hanoi. On dispose de trois tours ; sur la premire sont empils des disques de taille dcroissante. On veut dplacer ces disques sur lune des deux autres tours en respectant les deux rgles suivantes : on ne peut dplacer quun disque la fois ; on ne peut poser un disque sur un disque plus petit. Le comportement dune tour est similaire celui dune pile : on peut empiler ou dpiler des disques. Cependant on ne peut empiler un disque que sil est de diamtre infrieur au sommet courant de la tour.

Principes de base

21

Si lon utilise un langage classique qui offre lencapsulation, on est confront lalternative suivante : utiliser une pile pour reprsenter chaque tour, et sassurer que chaque appel de la procdure Empiler est prcd dun test vrifiant la validit de lempilement. crer un nouveau module, qui exporte un type opaque Tour et les procdures E m p i l e r , D p i l e r , et S o m m e t . Limplmentation de Tour est une pile ; la procdure Empiler ralise le contrle ncessaire avant dempiler un disque. Les procdures Dpiler et Sommet appellent leurs homologues de la pile. Aucune de ces deux solutions nest satisfaisante. La premire ne fournit pas dabstraction correspondant la notion de tour. La seconde est la seule acceptable du point de vue de labstraction mais prsente de multiples inconvnients : Il faut crire des procdures inutiles : Dpiler du module Tour ne fait quappeler Dpiler du module Pile ; Si lon ajoute une fonction Profondeur dans le module Pile, elle ne sera accessible pour lutilisateur de Tour que si lon dfinit galement une fonction Profondeur dans le module Tour comme on la fait pour Dpiler ; Le problme est encore plus grave si lon dcide denlever la fonction P r o f o n d e u r du module Pile : la fonction Profondeur de Tour appelle maintenant une fonction qui nexiste plus ; Il nest pas possible daccder directement limplmentation de la pile dans le module Tour, cause de lencapsulation. On ne peut pas ajouter la fonction Profondeur dans Tour sans dfinir une fonction quivalente dans Pile. Ce que lon cherche en ralit est la spcialisation dune pile pour en faire une tour, en crant un lien privilgi entre les modules Pile et Tour. Cest ce que permet lhritage par la dfinition dune classe Tour qui hrite de Pile.

22

Les langages objets

Pile pile sommet Empiler Dpiler Sommet

Tour

Empiler

Figure 4 - La classe Tour hrite de la classe Pile Dfinir une classe par hritage Lorsquune classe B hrite dune classe A, les instances de B contiennent les mmes champs que ceux de A, et les mthodes de A sont galement disponibles dans B. De plus, la sous-classe B peut : dfinir de nouveaux champs, qui sajoutent ceux de sa classe de base A ; dfinir de nouvelle mthodes, qui sajoutent celles hrites de A ; redfinir des mthodes de sa classe de base A. Enfin, les mthodes dfinies ou redfinies dans B ont accs aux champs et mthodes de B, mais aussi ceux de A. Ces proprits montrent le lien privilgi qui unit B A. En particulier, si lon ajoute des champs et/ou des mthodes A, il nest pas ncessaire de modifier B. Il en est de mme si lon retire des champs et/ou des mthodes de A, sauf bien sr sils taient utiliss dans les mthodes dfinies ou redfinies dans B.

Principes de base

23

Dans notre exemple, on se bornera redfinir la mthode Empiler, pour faire le contrle de la taille des disques et appeler la mthode Empiler de Pile si lopration est valide (voir figure 4). Dans ce cas, on dira que lon a spcialis la classe Pile car on a seulement redfini lune de ses mthodes. Si lon dfinissait de nouvelles mthodes dans la classe Tour (par exemple initialiser la tour avec N disques de taille dcroissante), on aurait enrichi la classe Pile. Ce simple exemple montre dj que lhritage peut servir raliser deux oprations : la spcialisation et lenrichissement. Larbre dhritage Telle que nous lavons prsente, la notion dhritage induit une fort darbres de classes : une classe A reprsente par un nud dun arbre a pour sous-classes les classes reprsentes par ses fils dans larbre. Les racines des arbres de la fort sont les classes qui nhritent pas dune autre classe. Si C hrite de B et B hrite de A, on dira par extension que C hrite de A. On dira indiffremment : B hrite de A B est une sous-classe de A B drive de A B est une classe drive de A A est une (la) superclasse de B A est une (la) classe de base de B Dans les deux dernires phrases, on emploie larticle dfini pour indiquer que A est lantcdent direct de B dans larbre dhritage. Certains langages imposent une classe de base unique pour toutes les autres, appele souvent Objet. Dans ce cas, la relation dhritage dfinit un arbre et non une fort. Par abus de langage, on parle dans tous les cas de larbre dhritage.

24

Les langages objets

2.4 LHRITAGE MULTIPLE


Lhritage que nous avons dfini est dit hritage simple car une classe a au plus une classe de base. Une gnralisation de lhritage simple, lhritage multiple, consiste autoriser une classe hriter directement de plusieurs classes. Ainsi si la classe B hrite de A1, A2, An, on a les proprits suivantes : les champs des instances de B sont lunion des champs de A1, A2, An et des champs propres de B ; les mthodes dfinies pour les instances de B sont lunion des mthodes dfinies dans A1, A2, An et des mthodes dfinies dans B. B peut galement redfinir des mthodes de ses classes de base. Larbre ou la fort dhritage devient alors un graphe. Pour viter des dfinitions rcursives on interdit davoir des cycles dans le graphe dhritage. En dautres termes, on interdit une classe dtre sa propre sous-classe, mme indirectement. Cette extension, apparemment simple, cache en fait de multiples difficults, qui ont notamment trait aux problmes de collision de noms dans lensemble des champs et mthodes hrites. Certaines de ces difficults ne pourront tre dveloppes que dans la description plus dtaille des chapitres suivants. Une difficult intrinsque de lhritage multiple est la gestion de lhritage rpt dune classe donne : si B et C hritent de A par un hritage simple, et D hrite de B et de C, alors D hrite deux fois de A , par deux chemins D-B-A et D-C-A dans le graphe dhritage (figure 5). Faut-il pour autant quune instance de D contienne deux fois les champs dfinis dans A, ou bien faut-il les partager ?

Principes de base

25

A a B b D d a (D.B.A) b (D.B) a (D.C.A) c (D.C) d (D) a (D.B.A, D.C.A) b (D.B) c (D.C) d (D) C c

Figure 5 - Deux interprtations de lhritage multiple Selon les situations, on souhaitera lune ou lautre solution, comme le montrent les deux exemples suivants. Soit A la classe des engins moteur, qui contient comme champs les caractristiques du moteur. Soit B la classe des automobiles et C la classe des grues. D est donc la classe des grues automobiles. Les deux interprtations de lhritage multiple sont possibles : si lon hrite deux fois de la classe des engins moteur, la grue automotrice a deux moteurs : lun pour sa partie automobile, lautre pour sa partie grue. Si lon hrite une seule fois de lengin moteur, on a un seul moteur qui sert la fois dplacer le vhicule et manuvrer la grue. Soit maintenant A la classe des objets mobiles, contenant les champs position et vitesse. Soit B la classe des bateaux, qui contient par exemple un champ pour le tonnage, et soit C la classe des objets propulss par le vent, qui contient un champ

26

Les langages objets

pour la surface de la voile. D est alors la classe des bateaux voile. Une instance de D a bien entendu une seule position et une seule vitesse, et dans ce cas il faut partager les champs de A hrits par diffrents chemins. Lhritage multiple noffre pas de rponse satisfaisante ce problme. La premire interprtation correspond une composition de classes, la seconde une combinaison. Le mcanisme de lhritage est certainement imparfait pour capturer la fois les notions de spcialisation, denrichissement, de composition et de combinaison. Mais lhritage nest pas le seul moyen de dfinir des classes ! Lun des piges qui guettent le programmeur utilisant un langage objets est la mauvaise utilisation de lhritage. La question quil faut se poser chaque dfinition de classe est la suivante : faut-il que B hrite de A , ou bien B doit-elle contenir un champ qui soit une instance de A ? B est-il une sorte de A ou bien B contient-il un A ? Nous reviendrons au chapitre 6 sur la pratique de la programmation par objets, et en particulier sur ces problmes.

2.5 LE POLYMORPHISME
La notion de polymorphisme recouvre la capacit pour un langage de dcrire le comportement dune procdure de faon indpendante de la nature de ses paramtres. Ainsi la procdure qui change les valeurs de deux variables est polymorphe si lon peut lcrire de faon indpendante du type de ses paramtres. De faon similaire la procdure Empiler est polymorphe si elle ne dpend pas du type de la valeur empiler. Comme le polymorphisme est dfini par rapport la notion de type, il ne concerne que les langages typs. On distingue plusieurs types de polymorphisme, selon la technique employe pour sa mise en uvre : Le polymorphisme ad hoc consiste crire plusieurs fois le corps de la procdure, pour chacun des types de paramtres souhaits. Cest ce que lon appelle souvent la surcharge : on

Principes de base

27

peut dfinir Echanger (Entier, Entier) et Echanger (Disque, Disque) de faon indpendante, de mme que Empiler (Pile, Entier) et Empiler (Pile, Disque). Ce type de polymorphisme est gnralement rsolu de faon statique ( la compilation). Il utilise le systme de types pour dterminer quelle incarnation de la procdure il faut appeler, en fonction des types effectifs des paramtres de lappel. Le polymorphisme dinclusion est fond sur une relation dordre partiel entre les types : si le type B est infrieur selon cette relation dordre au type A, alors on peut passer un objet de type B une procdure qui attend un paramtre de type A. Dans ce cas la dfinition dune seule procdure dfinit en ralit une famille de procdures pour tous les types infrieurs aux types mentionns comme paramtres. Si Entier et Disque sont tous deux des types infrieurs Objet, on pourra dfinir les procdure Echanger (Objet, Objet) et Empiler (Pile, Objet). Le polymorphisme paramtrique consiste dfinir un modle de procdure, qui sera ensuite incarn avec diffrents types. Il est implment par la gnricit, qui consiste utiliser des types comme paramtres. Ainsi si lon dfinit la procdure Echanger (<T>, <T>), on pourra lincarner avec <T> = Entier ou <T> = Disque. On peut faire de mme pour Empiler (Pile, <T>). Ces trois types de polymorphisme existent dans divers langages classiques. En Pascal le polymorphisme existe mais nest pas accessible lutilisateur ; on ne peut donc pas le qualifier puisque sa mise en uvre est implicite. Par exemple les oprateurs arithmtiques sont polymorphes : laddition, la soustraction, etc. sappliquent aux entiers, aux rels, et mme aux ensembles. De mme, les procdures dentre-sortie read et write sont polymorphes puisquelles sappliquent diffrents types de paramtres. Ada offre un polymorphisme ad hoc par la possibilit de surcharge des noms de procdures et des oprateurs. Il offre

28

Les langages objets

galement un polymorphisme paramtrique par la possibilit de dfinir des fonctions gnriques. En revanche le polymorphisme dinclusion est limit car trs peu de types sont comparables par une relation dordre. Le polymorphisme dans les langages objets La dfinition du polymorphisme est dpendante de la notion de type. Pourtant, tous les langages objets ne sont pas typs. Un langage objets typ est un langage objets dans lequel chaque classe dfinit un type, et dans lequel on dclare explicitement les types des objets que lon utilise. Les langages objets typs fournissent naturellement le polymorphisme ad hoc et le polymorphisme dinclusion. Certains langages offrent le polymorphisme paramtrique mais il ne fait pas partie des principes de base prsents dans ce chapitre. Le polymorphisme ad hoc provient de la possibilit de dfinir dans deux classes indpendantes (cest--dire nayant pas de relation dhritage) des mthodes de mme nom. Le corps de ces mthodes est dfini indpendamment dans chaque classe, mais du point de vue de lutilisateur, on peut envoyer le mme message deux objets de classes diffrentes. Ce polymorphisme ad hoc est intrinsque aux langages objets : il ne ncessite aucun mcanisme particulier, et dcoule simplement du fait que chaque objet est responsable du traitement des messages quil reoit. La dfinition de plusieurs mthodes de mme nom dans une mme classe ou dans des classes ayant une relation dhritage est une forme de polymorphisme ad hoc qui nest pas implicite dans les langages objets, bien que la plupart dentre eux offrent cette possibilit de surcharge. De plus, la redfinition dune mthode dans une classe drive, avec le mme nom et les mmes paramtres que dans la classe de base, ne constitue pas une surcharge mais une redfinition de mthode, comme nous lavons vu dans la description de lhritage (section 2.5).

Principes de base

29

Les langages objets disposent galement naturellement dun polymorphisme dinclusion que lon appelle aussi polymorphisme dhritage. En effet, la hirarchie des classes (dans le cas de lhritage simple) induit un ordre partiel : si B hrite de A (directement ou indirectement), alors B est infrieur A. Toute mthode de A est alors applicable un objet de classe B : cest ainsi que lon a dfini lhritage des mthodes. Le polymorphisme dhritage nous permet donc dappliquer la mthode Sommet de la classe Pile un objet de la classe Tour, puisque Tour est une sous-classe de Pile. Le polymorphisme dhritage sapplique galement lhritage multiple, en dfinissant une relation dordre partiel compatible avec le graphe dhritage de la faon suivante : une classe B est infrieure une classe A si et seulement si il existe un chemin orient de B vers A dans le graphe dhritage. Le graphe tant sans cycle, on ne peut avoir la fois un chemin orient de A vers B et un chemin orient de B vers A, ce qui assure la proprit dantisymtrie. Le polymorphisme dhritage sapplique non seulement au receveur des messages, mais galement au passage de paramtres des mthodes : si une mthode prend un paramtre formel de classe A, on peut lui passer un paramtre rel de classe B si B est infrieur A. Ainsi la mthode Empiler prend un paramtre de classe Entier. On peut lui passer un paramtre de classe Disque, si Disque hrite de Entier. Liaison statique et liaison dynamique Le polymorphisme dhritage interdit aux langages objets un typage exclusivement statique : un objet dclar de classe A peut en effet contenir, lexcution, un objet dune sous-classe de A. Les langages objets sont donc au mieux fortement typs, ce qui a des consquences importantes pour la compilation de ces langages. Dans un langage typage statique, le compilateur peut dterminer quelle mthode de quelle classe est effectivement appele lors dun envoi de message : on appelle cette technique la liaison statique.

30

Les langages objets

Lorsque le typage statique ne peut tre ralis, on doit avoir recours la liaison dynamique, cest--dire la dtermination lexcution de la mthode appeler. La liaison dynamique fait perdre un avantage important des langages compils : lefficacit du code engendr par le compilateur. La liaison dynamique doit aussi tre utilise dans les langages non typs, car labsence de systme de types interdit toute dtermination a priori de la mthode invoque par un envoi de message. Dans les deux cas, nous verrons les techniques employes pour rendre la liaison dynamique efficace. Les liens troits entre polymorphisme, typage, et mode de liaison dterminent en grande partie les compromis raliss par les diffrents langages objets entre puissance dexpression du langage, scurit de programmation, et performance lexcution. De ce point de vue, il nexiste pas aujourdhui de langage idal, et il est probable quil ne puisse en exister.

2.6 LES MTACLASSES


Nous avons dfini jusqu prsent la notion dobjet de faon assez vague : un objet doit appartenir une classe. Certains langages permettent de considrer une classe comme un objet ; en temps quobjet, cette classe doit donc tre linstance dune classe. On appelle la classe dune classe une mtaclasse (voir figure 6). La description que nous avons donne dune classe ressemble effectivement celle dun objet : une classe contient la liste des noms des champs de ses instances et le dictionnaire des mthodes que lon peut invoquer sur les instances. La liste des champs dune mtaclasse a donc deux lments : la liste des noms de champs et le dictionnaire des mthodes. Linstanciation est une opration qui est ralise par une classe ; cest donc une mthode de la mtaclasse. Une mtaclasse peut galement stocker dautres champs et dautres mthodes. Ainsi, larbre dhritage tant une relation

Principes de base

31

Mtaclasse

mtaclasses Base Drive classes

objets

Figure 6 - La notion de mtaclasse entre les classes, chaque classe contient un champ qui dsigne sa classe de base (reprsent par les flches grises paisses dans les figures). Une mthode de la mtaclasse permet de tester si une classe est sous-classe dune autre classe. Plusieurs modles de mtaclasses existent. Le plus simple consiste avoir une seule mtaclasse (appele par exemple Mtaclasse). Le plus complet permet de dfinir arbitrairement des mtaclasses. Cela autorise par exemple la redfinition de linstanciation ou lajout de mthodes de classes (dfinies dans la mtaclasse). Un modle intermdiaire, celui de Smalltalk-80, prvoit exactement une mtaclasse par classe. Lenvironnement de programmation se charge de crer automatiquement la mtaclasse pour toute classe cre par le programmeur. Ce dernier peut dfinir des mthodes de classes, qui sont stockes dans le dictionnaire de la mtaclasse. Cette approche rend les mtaclasses pratiquement transparentes pour le programmeur, et offre un compromis satisfaisant dans la plupart des applications.

32

Les langages objets

Dans tous les cas, la notion de mtaclasse induit une rgression linfini : en effet, une mtaclasse est une classe, donc un objet, et a donc une classe ; cette classe a donc une mtaclasse, qui est un objet, etc. Cette rgression est court-circuite par une boucle dans larbre dinstanciation. Par exemple, la classe Mtaclasse est sa propre mtaclasse. Les mtaclasses ont deux applications bien diffrentes. La premire est de permettre une dfinition mta-circulaire dun langage et de rendre accessible ses propres structures dexcution. On appelle cela la rification. Cette proprit existe galement en Lisp et permet dcrire trs simplement un interprte Lisp en Lisp. Un langage rifi permet galement de construire facilement des moyens dintrospection pour aider la mise au point des programmes : trace des envois de message et des invocations de mthodes, trace des changements de valeur des variables, etc. La deuxime application des mtaclasses est de permettre la construction dynamique de classes. Prenons lexemple dune application graphique interactive dans laquelle lutilisateur peut crer de nouveaux objets graphiques utilisables comme les objets primitifs (cercles, rectangles, etc.). Chaque nouvel objet graphique, lorsquil est transform en modle, donne lieu la cration dune nouvelle classe. Cette nouvelle classe est cre par instanciation dune mtaclasse existante. En labsence de mtaclasses, il faudrait dune faon ou dune autre simuler ce mcanisme, ce qui peut tre fastidieux. Disposer de mtaclasses dans un langages objets signifie que lon peut dynamiquement ( lexcution) dfinir de nouvelles classes et modifier des classes existantes. Cela interdit donc tout typage statique, et cest la raison pour laquelle les mtaclasses ne sont disponibles que dans les langages non typs. Certains langages typs utilisent nanmoins implicitement des mtaclasses, en autorisant par exemple la redfinition des mthodes dinstanciation. Il est galement possible de dfinir des objets qui jouent le rle des mtaclasses pour la reprsentation, lexcution, de larbre dhritage. Mais la

Principes de base

33

pleine puissance des mtaclasses reste rserve aux langages non typs.

Chapitre 3 LANGAGES OBJETS TYPS


Ce chapitre prsente les langages objets typs, dont Simula est lanctre. Ce dernier tant peu utilis aujourdhui, ce sont les langages plus rcents C++, Eiffel et Modula3 qui nous serviront de base. La premire version de C++ a t dfinie en 1983 par Bjarne Stroustrup aux Bell Laboratories, le mme centre de recherches o sont ns Unix et le langage C. Eiffel est un langage cr partir de 1985 par Bertrand Meyer de Interactive Software Engineering, Inc. Modula3 est une nouvelle version de Modula dveloppe depuis 1988 au Systems Research Center de DEC sous limpulsion de Luca Cardelli et Greg Nelson. Nous utilisons pour les exemples un pseudo-langage dont la syntaxe, inspire en grande partie de Pascal, se veut intuitive. Nous donnons ci-dessous la description de ce langage sous forme de notation BNF tendue, avec les conventions suivantes : les mots cls du langage sont en caractres gras ; les autres terminaux sont en italique ;

36

Les langages objets

les crochets indiquent les parties optionnelles ; la barre verticale dnote lalternative ; les parenthses servent grouper des parties de rgles ; + indique la rptition au moins une fois ; * indique la rptition zro ou plusieurs fois ; les indicateurs de rptition peuvent tre suivis dune virgule ou dun point-virgule qui indique le sparateur utiliser lorsquil y a plus dun lment dans la liste ;
prog classe ::= ( classe | mthode )+ ::= id-cls = classe [id-cls +,] { [champs champs+] [mthodes mthodes+ ] } ::= id-champ +, : type ; ::= procdure id-proc (param*;) ; | fonction id-fonc (param*;) : type ; ::= id-cls | entier | boolen | tableau [ const .. const ] de type ::= id-param +, : type ::= procdure id-cls.id-proc (param*;) bloc | fonction id-cls.id-fonc (param*;): type bloc ::= { [decl+] instr*; } ::= id-var +, : type ; ::= | | | | | | var := expr [var.]id-proc (expr*,) tantque expr-bool faire instr si expr-bool alors corps [sinon instr] pour id-var := expr expr faire instr retourner [expr] bloc

champs mthodes type param mthode bloc decl instr

var id

::= id(.id-champ)* | var [ expr ] ::= id-var | id-param | id-champ

Langages objets typs

37

expr

::= var | const | [var.]id-fonc (expr*,) | expr ( + | - | * | / ) expr ::= expr ( < | > | = | ) expr | expr-bool ( et | ou ) expr-bool | non expr-bool

expr-bool

Pour complter cette description, il convient de prciser que les commentaires sont introduits par deux tirets et se poursuivent jusqu la fin de la ligne.

3.1 CLASSES, OBJETS, MTHODES


Dfinir une classe La notion de classe dobjets est une extension naturelle de la notion de type enregistrement. En effet, une classe contient la description dune liste de champs, complte par la description dun ensemble de mthodes. Notre classe Pile peut scrire :
Pile = classe { champs pile : tableau [1..N] de entier; sommet : entier; mthodes procdure Empiler (valeur: entier); procdure Dpiler (); fonction Sommet () : entier; }

La dclaration dun objet correspond linstanciation :


p1 : Pile;

Linvocation des mthodes dun objet utilise loprateur point ( . ), qui permet traditionnellement laccs un champ

38

Les langages objets

dun enregistrement. On peut donc considrer que les mthodes se manipulent comme des champs de lobjet :
p1.Empiler (10); p1.Empiler (15); p1.Dpiler (); s := p1.Sommet ();

-- s vaut 10

Cette notation indique clairement quel est le receveur du message (ici p1), la mthode invoque, et ses paramtres. Comme lenvoi de message ncessite imprativement un receveur, on ne peut invoquer les mthodes autrement que par cette notation pointe :
Empiler (10)

na pas de sens car on ne connat pas le receveur du message, sauf sil y a un receveur implicite, comme nous le verrons plus loin. Cette mme notation pointe permet daccder aux champs de lobjet :
p1.pile [5];

Les rgles de visibilit empchent gnralement un tel accs aux champs. Comme nous lavons vu au chapitre 2, les champs sont dun accs priv tandis que les mthodes sont dun accs public. Ceci signifie que les champs dun objet sont accessibles seulement par cet objet, alors que les mthodes sont accessibles par tout objet grce lenvoi de message. Dfinir des mthodes La dclaration dune classe contient les en-ttes des mthodes. Nous allons dcrire leurs corps de faon spare. Pour cela, nous utiliserons la notation classe.mthode qui permet une qualification complte de la mthode. En effet, deux mthodes de mme nom peuvent tre dfinies dans deux classes diffrentes. Rappelons que cela constitue la premire forme de polymorphisme offerte par les langages objets, le polymorphisme ad hoc.

Langages objets typs

39

Selon les langages, la faon de dclarer les corps des mthodes varie. La notation que nous avons choisie est inspire de C++. Eiffel adopte une autre convention qui consiste mettre les dclarations de mthodes dans un bloc associ la classe o elles sont dfinies, ce qui pourrait tre transcrit de la faon suivante dans notre pseudo-langage :
Pile = classe { procdure Empiler (valeur : entier) { -- corps de Empiler } -- etc. }

La notation qualifie que nous avons adopte ici permet de sparer la dclaration de la classe de la dclaration des corps des mthodes, mais les deux mcanismes sont strictement quivalents. Dans notre exemple, si lon omet les tests de validit des oprations (pile vide, pile pleine), on a les dfinitions de corps de mthodes suivantes :
procdure Pile.Empiler (valeur: entier) { -- attention : pas de test de dbordement sommet := sommet + 1; pile [sommet] := valeur; } procdure Pile.Dpiler () { -- attention : pas de test de pile vide sommet := sommet - 1; } fonction Pile.Sommet () : entier { retourner pile [sommet]; }

Une mthode est toujours invoque avec un objet receveur, qui sert de contexte son excution. Laccs aux champs de lobjet receveur (pile et sommet dans notre exemple) se fait en mentionnant directement leurs noms.

40

Les langages objets

Pour tre plus prcis, les entits accessibles depuis une mthode sont : lobjet receveur, les champs de lobjet receveur, les mthodes de lobjet receveur, les paramtres de la mthode, les variables locales de la mthode, les objets dclars de faon globale au programme. Les champs des objets et les paramtres tant eux-mmes des objets, on peut invoquer leurs mthodes. Pour illustrer cela, supposons lexistence dune classe Fichier et crivons de nouvelles mthodes pour la classe Pile (ces mthodes doivent tre ajoutes la dclaration de la classe Pile) :
procdure Pile.crire (sortie : Fichier) { pour i := 1 sommet faire sortie.crire (pile [i]); } procdure Pile.Vider () { tantque sommet > 0 faire Dpiler (); }

Pile.crire invoque la mthode crire du paramtre sortie. Elle crit sur ce fichier lensemble des lments de la pile. Nous supposons ici que c r i r e est une mthode de la classe Fichier.Pile.Vider invoque la mthode Dpiler sans la qualifier par un receveur, ce qui semble contraire ce que nous avons dit plus haut. Mais ici on est dans le contexte dun objet receveur de classe Pile, qui devient le receveur implicite de Dpiler (voir figure 7). Cest par un mcanisme identique que s o m m e t reprsente le champ sommet de lobjet receveur du message. La pseudo-variable moi Bien que lobjet receveur soit implicite en ce qui concerne laccs aux champs et aux mthodes, il est parfois ncessaire de le rfrencer explicitement, par exemple pour le passer en paramtre une autre mthode. Selon les langages, il porte le

Langages objets typs

41

1 Vider Pile pile sommet 2 3 4


Vider ( ) { tant que sommet > 0 faire Dpiler ( ); } Dpiler ( ) { }

Figure 7 - Accs aux champs et aux mthodes. Les flches hachures reprsentent linvocation de mthode nom rserv de self (Modula3, mais aussi Smalltalk), Current (Eiffel), this (C++). Nous lappellerons moi . Moi nest pas proprement parler un objet, mais une faon de rfrencer le receveur de la mthode en cours dexcution. On utilise pour cela le terme de pseudo-variable. Lexemple suivant illustre lutilisation de moi. Les classes Sommet et Arc permettent de reprsenter un graphe. Un sommet est reli un ensemble darcs, et un arc relie deux sommets.
Sommet = classe { champs -- reprsentation des arcs adjacents mthodes procdure Ajouter (a : Arc); } Arc = classe { champs dpart, arrive : Sommet; mthodes procdure Relier (s1, s2 : Sommet); }

42

Les langages objets

crivons le corps de la mthode Relier de la classe Arc :


procdure Arc.Relier (s1, s2 : Sommet) { dpart := s1; arrive := s2; s1.Ajouter (moi); s2.Ajouter (moi); }

Cet exemple montre quil est indispensable de pouvoir rfrencer explicitement le receveur du message dans le corps de la mthode invoque : cest larc qui reoit le message Relier qui doit tre ajout aux sommets s1 et s2. On peut galement utiliser la pseudo-variable moi pour qualifier les champs et les mthodes locales, mais cela napporte rien sinon une notation plus lourde. La mthode Vider de la classe Pile pourrait ainsi scrire comme suit :
procdure Pile.Vider () { tantque moi.sommet > 0 faire moi.Dpiler (); }

Les classes primitives Nous avons utilis pour dfinir la classe Pile un champ de type entier et un champ de type tableau, considrant ces types comme prdfinis dans le langage. Le statut de ces types prdfinis varie dun langage lautre. En gnral, les types atomiques (entier, boolen, caractre) ne sont pas des classes et on ne peut en hriter. Les types structurs comme les tableaux sont parfois accessibles comme des classes gnriques. On peut toujours construire une classe qui contient un champ dun type prdfini. Malheureusement, moins de disposer de mcanismes de conversion implicite entre types atomiques et classes, on ne peut utiliser ces classes de faon transparente. Par exemple, si lon dfinit la classe Entier, contenant un champ de type entier, comme suit :

Langages objets typs

43

Entier = classe { champs valeur : entier; mthodes procdure Valeur (v : entier); } procdure Entier.Valeur (v : entier) { valeur := v; }

et si lon change le type entier par la classe Entier dans la classe Pile, on ne peut plus crire
p1.Empiler (10);

car 10 est une valeur du type prdfini entier, et non un objet de la classe Entier. Sans mcanisme particulier du langage, il faut crire :
v : Entier; v.Valeur (10); p1.Empiler (v);

La diffrence de statut entre types atomiques et classes rsulte dun compromis dans lefficacit de limplmentation des langages objets typs. Cette diffrence ne pose que peu de problmes dans la pratique, bien quelle soit peu satisfaisante pour lesprit.

3.2 HRITAGE
Nous allons maintenant prsenter comment est mis en uvre lun des concepts de base des langages objets : lhritage. Nous allons pour cela prsenter les deux principales utilisations de lhritage, la spcialisation et lenrichissement. Spcialisation par hritage Nous dfinissons maintenant une sous-classe de la classe Pile, la classe Tour. Rappelons quune tour est une pile dont les valeurs sont dcroissantes.

44

Les langages objets

Tour = classe Pile { mthodes procdure Initialiser (n : entier); fonction PeutEmpiler (valeur : entier) : boolen; procdure Empiler (valeur : entier); }

Lhritage est mentionn dans len-tte de la dclaration (comparer avec la dclaration de la classe Pile). La procdure Initialiser empile n disques de tailles dcroissantes :
procdure Tour.Initialiser (n : entier) { sommet := 0; pour i := n 1 faire Empiler (i); }

Initialiser invoque la mthode Empiler, qui est redfinie dans la classe Tour. Dfinissons maintenant les corps des mthodes PeutEmpiler et Empiler :
fonction Tour.PeutEmpiler (valeur : entier) : boolen { si sommet = 0 alors retourner vrai; sinon retourner valeur < Sommet (); }

La mthode PeutEmpiler rfrence le champ sommet de sa classe de base ainsi que la mthode Sommet dfinie galement dans la classe de base. Elle teste si la valeur peut tre empile, cest--dire si la tour est vide ou sinon si la valeur est plus petite que le sommet courant de la tour. Empiler utilise PeutEmpiler pour dcider de lempilement effectif de la valeur :
procdure Tour.Empiler (valeur : entier) { si PeutEmpiler (valeur) alors Pile.Empiler (valeur); sinon erreur.crire (impossible dempiler); }

On suppose ici lexistence dun objet global erreur, de la classe Fichier, qui permet de communiquer des messages lutilisateur grce sa mthode crire.

Langages objets typs

45

Pile

Empiler

Tour
Empiler (valeur: entier) { Pile. Empiler (valeur); }

Figure 8 - Spcialisation de la mthode Empiler Lappel de Pile.Empiler (valeur) mrite quelques explications. La classe Tour est une spcialisation de la classe Pile, cest-dire que lon a simplement redfini une mthode de la classe Pile. Dans cette situation, la mthode redfinie a souvent besoin de rfrencer la mthode de mme nom dans la classe de base. Si lon avait crit
Empiler (valeur)

on aurait provoqu un appel rcursif, puisque lon est dans le corps de la mthode Empiler de la classe Tour. La notation
Pile.Empiler (valeur)

permet de qualifier le nom de la mthode appele. Comme Tour hrite de Pile, la mthode Empiler de Pile est accessible dans le contexte courant, mais elle est cache par sa redfinition dans la classe Tour (voir figure 8). La notation qualifie permet laccs la mthode de la classe de base, dans le contexte de lobjet receveur. Elle ne peut tre utilise que dans cette situation. Une fois la classe Tour dfinie, on peut en dclarer des instances et invoquer des mthodes :
t : Tour; ... t.Empiler (10);

46

Les langages objets

t.Dpiler; t.Empiler (20); t.Empiler (5); -- impossible dempiler

Comme on la dit, les mthodes de la classe de base restent accessibles. Dans cet exemple, t.Dpiler invoque Pile.Dpiler. Enrichissement par hritage Nous allons maintenant dfinir une classe drive de la classe Tour en ajoutant la possibilit de reprsenter graphiquement la tour. Pour cela nous supposons lexistence des classes Fentre et Rectangle, avec les dfinitions partielles suivantes :
Fentre = classe { mthodes procdure Effacer (); } Rectangle = classe { mthodes procdure Centre (posX, posY : entier); procdure Taille (largeur, hauteur : entier); procdure Dessiner (f : Fentre); }

TourG est une sous-classe de Tour dfinie comme suit :


TourG = classe Tour { champs f : Fentre; x, y : entier; mthodes procdure Placer (posX, posY : entier); procdure Dessiner (); procdure Empiler (valeur: entier); procdure Dpiler (); }

Langages objets typs

47

Il sagit ici dun enrichissement : trois nouveaux champs indiquent la fentre de lcran dans laquelle sera affiche la tour, et la position de la tour dans cette fentre. Chaque tage de la tour sera reprsent par un rectangle de taille proportionnelle la valeur entire qui le reprsente dans la tour. Deux nouvelles mthodes permettent daffecter une position la tour et de dessiner la tour. Enfin, les mthodes Empiler et Dpiler sont redfinies afin dassurer que la tour est redessine chaque modification. Le corps des mthodes est dcrit ci-dessous. Placer affecte la position de la tour et la redessine.
procdure TourG.Placer (posX, posY : entier) { x := posX; y := posY; Dessiner (); }

Dessiner commence par effacer la fentre, puis redessine la tour tage par tage. Dessiner est similaire dans son principe la mthode crire dfinie auparavant dans la classe Pile.
procdure TourG.Dessiner () { rect : Rectangle; f.Effacer (); pour i := 1 sommet faire { rect.Centre (x, y - i); rect.Taille (pile [i], 1); rect.Dessiner (f); } }

Empiler et Dpiler invoquent la mthode de mme nom dans la classe de base Tour et redessinent la tour. On pourrait bien sr optimiser laffichage, mais ce nest pas lobjet de lexemple.
procdure TourG.Empiler (valeur: entier) { Tour.Empiler (valeur); Dessiner (); }

48

Les langages objets

procdure TourG.Dpiler () { Tour.Dpiler (); Dessiner (); }

Il est noter que Tour.Dpiler na pas t dfinie. En fait la classe Tour hrite Dpiler de la classe Pile, donc Tour.Dpiler est identique Pile.Dpiler. Nanmoins, il serait imprudent dutiliser Pile.Dpiler directement car on peut tre amen redfinir Dpiler dans Tour.

3.3 HRITAGE MULTIPLE


Lhritage multiple permet dutiliser plusieurs classes de base. La classe TourG, par exemple, reprsente une tour qui sait safficher dans une fentre. En changeant lgrement de point de vue, on peut considrer que la classe TourG est la fois une tour et une fentre. On a alors un hritage multiple de Tour et de Fentre (figure 9). Voyons la dfinition de la classe TourGM ainsi obtenue :
TourGM = classe Tour, Fentre { champs x, y : entier; mthodes procdure Placer (posX, posY : entier); procdure Dessiner (); procdure Empiler (valeur: entier); procdure Dpiler (); }

Lhritage multiple est mentionn en faisant apparatre la liste des classes de base dans len-tte de la dclaration. Le champ f napparat plus : il est remplac par lhritage de Fentre. La seule mthode qui change par rapport la classe TourG est la mthode Dessiner :

Langages objets typs

49

Pile

Tour

Fentre

Tour GM

Figure 9 - Hritage multiple de la classe TourGM


procdure TourGM.Dessiner () { rect : Rectangle; Effacer (); pour i := 1 sommet faire { rect.Centre (x, y - i); rect.Taille (pile [i], 1); rect.Dessiner (moi); } }

On constate que linvocation de la mthode Effacer nest plus qualifie par le champ f. En effet, cette mthode est maintenant hrite de la classe Fentre. Par ailleurs, linvocation de la mthode Dessiner prend comme argument la pseudo-variable moi. En effet, TourGM hrite maintenant de Fentre, donc une T o u r G M est une fentre : on utilise ici le polymorphisme dhritage sur le paramtre de la mthode Dessiner. Bien que les diffrences entre les implmentations de TourG et TourGM soient minimes, leffet de lhritage multiple est plus important quil ny parat. En effet, alors que TourG nhrite

50

Les langages objets

que des mthodes de Tour, TourGM hrite de celles de Tour et de Fentre. Il est donc tout fait possible dcrire :
tgm : TourGM; tgm.Placer (100, 100); tgm.Empiler (20); tgm.Empiler (10); tgm.Effacer ();

Linvocation dEffacer est correcte puisque Effacer est hrite de Fentre. Pour un objet de la classe TourG, cet envoi de message aurait t illicite. On voit donc que le choix dimplmenter une tour graphique par la classe TourG ou la classe T o u r G M dpend du contexte dutilisation dans lapplication. Lhritage, comme lhritage multiple, ne doit pas tre utilis comme une facilit dimplmentation, mais comme une faon de spcifier des liens privilgis entre classes. Lhritage multiple cre cependant une ambigut : supposons que la classe Fentre dfinisse une mthode crire, qui imprime son tat. La classe Tour de son ct hrite une mthode crire de la classe Pile. Que se passe-t-il si lon crit :
tgm.crire (fich);

Il y a un conflit car la classe TourGM hrite deux fois de la mthode crire. Les langages rsolvent ce problme de diffrentes manires : lordre de lhritage multiple dtermine une priorit entre les classes ; dans notre cas TourGM hrite dabord de Tour, puis de Fentre, donc Tour.crire masque Fentre.crire. Le rsultat est donc dimprimer la tour, cest--dire la pile. il faut qualifier linvocation de la mthode, par exemple tgm.Fentre.crire (fich). Cela suppose donc que lutilisateur connaisse le graphe dhritage, ce qui ne favorise pas lide dencapsulation et dabstraction. Cest le mcanisme choisi par Modula3 et C++. il faut renommer, lors de lhritage, les mthodes qui engendrent des conflits. On peut ainsi hriter de Tour, et de

Langages objets typs

51

Fentre en renommant la mthode crire hrite de fentre en crireF. tgm.crire(fich) imprime donc la tour, alors que tgm.crireF(fich) imprime la fentre. Cest le mcanisme impos par Eiffel, et disponible en C++. il faut dfinir une mthode crire dans la classe TourGM, qui lvera le conflit en masquant les deux mthodes crire. La dernire mthode est toujours ralisable. Dans notre exemple, on pourrait dfinir cette mthode de la faon suivante :
procdure TourGM.crire (sortie : Fichier) { Tour.crire (sortie); Fentre.crire (sortie); sortie.crire (x); sortie.crire (y); }

Cette mthode crit la partie Tour, la partie Fentre et les champs propres de T o u r G M . Les invocations qualifies Tour.crire et Fentre.crire lvent lambigut en mme temps quelles vitent lappel rcursif de TourGM.crire. Lorsque des champs de plusieurs classes de base portent le mme nom, les mmes problmes de conflits daccs se posent. Ils sont rsolus par un accs qualifi (en C++) ou par renommage (en Eiffel). Nous avons voqu au chapitre prcdent dautres problmes concernant lhritage multiple, et notamment lhritage rpt : que se passe-t-il si une classe hrite, directement ou indirectement, plusieurs fois de la mme classe ? Faut-il dupliquer les champs de cette classe ou doivent-ils apparatre une seule fois ? En C++, la notion de classe de base virtuelle permet de ne voir quune fois une classe de base qui est accessible par plusieurs chemins dhritage. En Eiffel, le contrle est plus fin car chaque champ dune classe de base hrite plusieurs fois peut tre dupliqu ou partag, selon que le champ est renomm ou non.

52

Les langages objets

3.4 LIAISON DYNAMIQUE


Tel que nous lavons prsent, lhritage des mthodes dans un langage objets typ permet de dterminer de faon statique les invocations de mthodes : pour un objet o de la classe A, lappel
o.m (paramtres)

est rsolu en recherchant dans la classe A une mthode de nom m. Si elle nest pas trouve, la recherche se poursuit dans la classe de base de A, et ainsi de suite jusqu trouver la mthode ou atteindre la racine de larbre dhritage. Si la mthode est trouve, linvocation de mthode est correcte, sinon elle est errone. Le compilateur peut profiter de cette recherche, destine vrifier la validit du programme, pour engendrer le code qui appelle directement la mthode trouve. Cela vite une recherche similaire lexcution et rend donc le programme plus efficace. Cela sappelle la liaison statique. Malheureusement, le polymorphisme dhritage rend cette optimisation invalide, comme le montre lexemple suivant :
t : Tour ; tg : TourG ; ... t := tg ; t.Empiler (10) ; -- que se passe-t-il ?

Laffectation de la tour graphique tg la tour simple t est correcte en vertu du polymorphisme dhritage : une tour graphique est un cas particulier de tour, donc un objet de type tour peut rfrencer une tour graphique. En utilisant la liaison statique, le compilateur rsout linvocation dEmpiler par lappel de Tour.Empiler, car t est dclar de type Tour. Mais t contient en ralit, lors de lexcution, un objet de classe TourG, et linvocation de Tour.Empiler est invalide : il aurait fallu invoquer TourG.Empiler.

Langages objets typs

53

Dans cet exemple, le typage statique ne nous permet pas de savoir que t contient en ralit un objet dune sous-classe de Tour et la liaison statique brise la smantique du polymorphisme dhritage. Les mthodes virtuelles Ce problme avait t not ds Simula, et rsolu en introduisant la notion de mthode virtuelle : en dclarant Empiler virtuelle, on indique dutiliser une liaison dynamique et non plus une liaison statique : le contrle de type a toujours lieu la compilation, mais la dtermination de la mthode appeler a lieu lexcution, en fonction du type effectif de lobjet. On comprend aisment que la liaison dynamique est plus chre lexcution que la liaison statique, mais quelle est indispensable si lon veut garder la smantique du polymorphisme dhritage. Lexemple suivant montre une autre situation dans laquelle les mthodes virtuelles sont indispensables :
tg : TourG ; tg.Initialiser (4);

On initialise une tour graphique avec quatre disques. Nous allons voir que l encore, il faut que Empiler ait t dclare virtuelle. La procdure Initialiser est hrite de Tour. Voici comment nous lavons dfinie :
procdure Tour.Initialiser (n : entier) { pour i := n 1 faire Empiler (i); }

Si Empiler nest pas dclare virtuelle, son invocation est rsolue par liaison statique par lappel de Tour.Empiler, puisque le receveur est considr de classe Tour. Lorsque lon invoque tg.Initialiser(4), le receveur sera en ralit de classe TourG, et cest la mauvaise version dEmpiler qui sera invoque (voir figure 10). En dclarant Empiler virtuelle, ce problme disparat. En loccurrence, cest TourG.Empiler qui sera invoque, provoquant le dessin de la tour au fur et mesure de son initialisation.

54

Les langages objets

Tour Empiler (v) {} Initialiser (n) { pour i := n 1 Empiler (i) } 2 Initialiser (4) 1 TourG tg Empiler (v) {} 3 Dynamique 3' Statique

Figure 10 - Liaison statique contre liaison dynamique Lutilisation extensive du polymorphisme dans les langages objets pourrait laisser penser que toutes les mthodes doivent tre virtuelles. Cest la solution choisie dans Eiffel et Modula3, qui assurent au programmeur que tout se passe comme si la liaison tait toujours dynamique. Par contre, C++ et Simula obligent dclarer explicitement les mthodes virtuelles comme telles. Cela offre au programmeur la possibilit de choisir entre la scurit de la liaison dynamique et lefficacit de la liaison statique, ses risques et prils. En pratique, on se rend compte quun nombre limit de mthodes ont effectivement besoin dtre virtuelles, mais quil est difficile de dterminer lesquelles, surtout lorsque les classes sont destines tre rutilises. Limplmentation de la liaison dynamique Limplmentation usuelle de la liaison dynamique consiste attribuer chaque mthode virtuelle un indice unique pour la

Langages objets typs

55

Pile Empiler Dpiler Code de Pile.Empiler Code de Pile.Dpiler

une Pile

une autre Pile

Tour Empiler Depiler Tables virtuelles Code de Tour.Empiler

une Tour

Figure 11 - Implmentation de la liaison dynamique hirarchie de classes laquelle elle appartient. Lors de lexcution, chaque classe est reprsente par une table, qui contient pour un indice donn ladresse de la mthode correspondante de cette classe. Chaque objet dune classe contenant des mthodes virtuelles contient ladresse de cette table (figure 11). Linvocation dune mthode exige simplement une indirection dans cette table. Le cot de limplmentation est donc le suivant : une table, dite table virtuelle, par classe ; un pointeur vers la table virtuelle par objet ; une indirection par invocation de mthode virtuelle. On peut considrer ce cot comme acceptable, surtout si on le compare au cot dinvocation des mthodes dans les langages non typs (dcrit la fin de la section 4.3 du chapitre 4). On peut aussi considrer quil est inutile de supporter ce cot systmatiquement, et cest la raison pour laquelle Simula et C++ donnent le choix (et la responsabilit) au programmeur de

56

Les langages objets

dclarer virtuelles les mthodes quil juge utile. Modula3, au contraire, tire parti de la table virtuelle ncessaire chaque objet pour autoriser un objet redfinir des mthodes : il suffit de lui crer une table virtuelle propre. On quitte alors le modle strict des langages de classes, puisque ce nest plus la classe qui dtient le comportement de toutes ses instances.

3.5 RGLES DE VISIBILIT


La dclaration explicite des types dans un langage assure la dtection derreurs ds la compilation, et permet donc au programmeur de se protger contre lui-mme. Un autre aspect de cette protection concerne les rgles de visibilit, cest--dire les mcanismes dencapsulation qui permettent de limiter laccs des donnes et mthodes. Le rle principal de lencapsulation est de masquer les dtails dimplmentation dune classe afin dviter que des clients extrieurs puissent la modifier impunment. On peut alors modifier a posteriori les parties caches sans effet perceptible pour les clients. Nous avons considr jusqu prsent que les rgles de visibilit taient les suivantes : Les champs dune classe sont visibles seulement dans le corps des mthodes de cette classe et de ses classes drives. Les mthodes dune classe sont visibles de lextrieur par tout client. Lunit de lencapsulation : classe ou objet La premire rgle ci-dessus est ambigu : soit une classe A et une mthode m de cette classe ; on peut comprendre la rgle de deux faons : dans le corps de m, on a accs aux champs de nimporte quel objet de classe A (en particulier le receveur de m) ; dans le corps de m , on na accs quaux champs de son receveur. Par exemple, si m a un paramtre de classe A, m na pas accs aux champs de ce paramtre.

Langages objets typs

57

Cette deuxime interprtation est plus restrictive. Elle correspond un domaine de visibilit qui est lobjet : un objet nest connu que des mthodes de sa classe, une mthode ne connat que les champs de son receveur. La premire interprtation correspond un domaine de visibilit qui est la classe tout entire : une classe connat ses instances, donc toute mthode de cette classe connat toute instance de cette classe. Cette interprtation est celle utilise dans les langages de la famille Simula. linverse, les langages de la famille Smalltalk adoptent gnralement la premire interprtation, et considrent donc lobjet comme lunit de lencapsulation. Une fois dfinie cette notion de domaine de visibilit, il reste montrer les diffrents mcanismes offerts par les langages. Modula3, C++ et Eiffel prsentent de ce point de vue des approches diffrentes. Modula3 : visible ou cach Par dfaut, tous les champs et mthodes dune classe Modula3 sont visibles de nimporte quel client. Nanmoins, un mcanisme permet de crer un type opaque identique une classe donne, dont on ne rend visible que les mthodes souhaites. Ce mcanisme, assez lourd, oblige crer au moins deux types par classe : un type ouvert contenant les champs et mthodes, et un type ferm, utilis par les clients, ne prsentant que les mthodes utiles. Dun autre ct, cette technique permet de dfinir plusieurs interfaces une classe donne en dfinissant plusieurs types limitant laccs cette classe de diffrentes manires. Ainsi on peut imaginer quune classe drive souhaite un accs plus large sa classe de base quune classe quelconque. C++ : priv, protg ou public En C++, les mcanismes de visibilit sappliquent indiffremment aux champs et aux mthodes, que nous appellerons collectivement des membres dans cette section,

58

Les langages objets

conformment la terminologie C++. Le type de visibilit offert par C++ est intermdiaire entre ceux de Modula3 et Eiffel. Un membre dune classe peut tre dclar avec lun des trois niveaux de visibilit1 suivants : priv, protg, public. Un membre priv nest visible que depuis les mthodes de la classe dans laquelle il est dfini. Aucune classe extrieure ny a accs, pas mme une classe drive. Un membre public est au contraire accessible tout client. Enfin, un membre p r o t g nest accessible quaux mthodes de la classe et de ses classes drives. Cela entrine le fait quune classe drive est un client privilgi de sa classe de base. Ces niveaux de visibilit sont transmis par hritage : un membre public dune classe A est public dans toute classe drive de A. De mme, un membre protg dans A est protg dans toute classe drive de A. En revanche, un membre priv nest pas visible dans une classe hrite. C++ offre trois mcanismes complmentaires en ce qui concerne la visibilit : lhritage priv permet de construire une classe B drive dune classe A sans que ce lien dhritage ne soit visible de lextrieur : les membres hrits de A sont privs dans B, et il ny a pas de polymorphisme dinclusion entre B et A. une classe peut rexporter un membre hrit avec un niveau de visibilit diffrent. Par exemple, la mthode protge m dune classe A peut tre rendue publique dans une classe B drive de A. Dans le cas de lhritage priv, on peut ainsi rendre visibles certains membres de la classe de base. une classe peut avoir des classes amies et des mthodes amies : une classe B amie de A ou une mthode f amie de A a accs tous les membres de la classe A. Cela permet
1 En C++, on parle d'accessibilit plutt que de visibilit. Bien que la diffrence soit significative, nous n'entrerons pas dans les dtails ici.

Langages objets typs

59

douvrir une classe des clients privilgis, sans toutefois autoriser laccs une classe sans son consentement. En effet, cest la classe A qui doit dclarer que B et f sont amies. Eiffel : exportation explicite Le mcanisme le plus raffin pour ce qui concerne les rgles de visibilit est celui dEiffel : chaque classe dcrit la liste des membres quelle exporte. Chaque membre ainsi export peut tre qualifi par une liste de classes clientes. Par dfaut, un membre export est visible par nimporte quel client. Si lon qualifie le membre avec une liste de classes, alors ce membre nest visible que par ces classes et leurs classes drives. Le contrle de la visibilit des membres hrits est ralis par le mme mcanisme. On peut retransmettre les membres hrits avec la visibilit dclare dans la classe de base, ou bien changer leur visibilit. Des progrs faire Diverses raisons dordre syntaxique peuvent faire prfrer tel ou tel mcanisme de visibilit. Dans tous les cas, il nest pas facile de matriser la combinaison entre la visibilit, lhritage et le polymorphisme. La diversit syntaxique cache un rel problme smantique : on ne matrise pas aujourdhui les notions de visibilit de faon satisfaisante, et cest la raison pour laquelle il nexiste pas de mcanisme simple qui rponde des besoins par ailleurs mal dfinis : la visibilit doit tre compatible avec lhritage, mais des classes sans relation dhritage doivent pouvoir se connatre de faon privilgie, comme les amis de C++. Ds lors, un mcanisme global est ncessaire mais pas suffisant. Des travaux sur la notion de vue, assez proche du mcanisme de Modula3, sont prometteurs : ils permettent de dfinir plusieurs vues dune classe donne, cest--dire plusieurs interfaces. Un client choisit alors la vue qui lintresse. Cet aspect des langages objets nest

60

Les langages objets

donc pas fig, et lon peut sattendre de nouvelles solutions court terme.

3.6 MCANISMES SPCIFIQUES


Initialisation des objets Lun des problmes classiques dans les langages qui manipulent des variables (et, dans notre cas, des objets) est celui de linitialisation. Ainsi, en Pascal, une variable non initialise ne sera pas repre par le compilateur et pourra provoquer un comportement alatoire du programme. Mme lorsque les variables non initialises sont repres par le compilateur, le programmeur doit les initialiser explicitement, ce qui alourdit le programme. La notion dobjet offre un terrain favorable pour assurer linitialisation des objets. En effet, on peut imaginer quune mthode particulire prenne en charge automatiquement linitialisation de tout nouvel objet. C++ et Eiffel offrent de tels mcanismes. Une classe Eiffel peut dclarer une mthode spciale, de nom prdfini Create, qui assure linitialisation des objets de cette classe. Le programmeur doit invoquer explicitement cette mthode, qui a un statut particulier. Ainsi, linstruction o.Create invoque en ralit les mthodes Create de la classe de o et de chacune de ses classes de base. Cela assure que tous les composants de o sont initialiss correctement. Il sensuit que la mthode Create ne shrite pas ; le compilateur engendre au contraire une mthode C r e a t e pour les classes qui nen dfinissent pas. Cette mthode initialise chacun des champs une valeur par dfaut dpendant de son type. En C++, une classe peut dclarer des constructeurs, mthodes spciales qui portent le nom de la classe elle-mme. Divers constructeurs, avec des listes de paramtres diffrentes, permettent de dfinir plusieurs moyens dinitialisation. Un constructeur sans paramtre est un constructeur par dfaut. Lappel du constructeur est ralis automatiquement, par le

Langages objets typs

61

compilateur, chaque dclaration dobjet. Comme en Eiffel, le constructeur dune classe drive appelle automatiquement celui de sa classe de base. Par contre, linverse dEiffel, lappel du constructeur est implicite, ce qui vite les oublis malencontreux. De faon symtrique linitialisation, C++ permet la dfinition de mthodes spcifiques pour la destruction des objets : ces destructeurs sont, comme les constructeurs, invoqus automatiquement lorsquun objet devient inaccessible. Ainsi un objet dclar comme variable locale dune mthode voit son destructeur appel lorsque la mthode termine son excution. La destruction assure des objets est aussi importante que leur initialisation. Par exemple, si lon considre un objet reprsentant un fichier, le destructeur peut assurer la fermeture du fichier lorsque celui nest plus accessible. Le plus souvent, les destructeurs sont utiliss pour dtruire des structures dynamiques contenues dans les objets. Ainsi, une classe Liste, contenant une liste chane dobjets allous dynamiquement, peut assurer la destruction de ses lments dans son destructeur. Voici comment lon pourrait dfinir et utiliser un constructeur et un destructeur pour la classe Tour. Nous avons pour cela tendu la syntaxe de notre langage par lajout des mots cls constructeur et destructeur.
Tour = classe Pile { mthodes constructeur Tour (taille : entier); destructeur Tour (); } constructeur Tour (taille : entier) { Initialiser (taille); } destructeur Tour () { tantque sommet > 0 faire Dpiler (); }

62

Les langages objets

-- exemple dutilisation t : Tour (10); -- constructeur Tour (entier) -- appel du destructeur de t

Gnricit Tout au long de ce chapitre, nous avons utilis la classe Pile pour reprsenter une pile dentiers. Si lon voulait grer une pile dautres objets, il faudrait dfinir une nouvelle classe, probablement trs proche dans sa dfinition et son implmentation de la classe Pile. La gnricit, qui met en uvre le polymorphisme paramtrique, est un mcanisme attrayant pour dfinir des types gnraux. Par exemple, toute classe contenant une collection dobjets est un bon candidat pour une classe gnrique : tableau, liste, arbre, etc. En effet de telles classes conteneurs dpendent peu de la classe des objets contenus. La gnricit nous permet de dfinir une classe gnrique GPile, paramtre par le type de ses lments :
GPile = classe (T : classe) { champs pile : tableau [1..N] de T; sommet : entier; mthodes procdure Empiler (val : T); procdure Dpiler (); fonction Sommet () : T; } Pile = classe GPile (entier); p : Pile; p.Empiler (10); -- instanciation

On ne peut utiliser la classe G P i l e telle quelle : il faut linstancier en lui donnant le type de ses lments. En revanche, on peut driver GPile ; la classe drive est elle aussi gnrique. La gnricit et lhritage sont deux mcanismes qui permettent de dfinir des familles potentiellement infinies de

Langages objets typs

63

classes. Aucun nest rductible lautre, et un langage objets qui offre la gnricit est strictement plus puissant quun langage qui ne loffre pas. Eiffel permet la dfinition de classes gnriques. La gnricit est galement dfinie pour C++, mais elle nest pas implmente dans les compilateurs actuels. Gestion dynamique des objets Nous avons introduit la notion dobjet dans ce chapitre en gnralisant la notion denregistrement prsente dans des langages tels que Pascal. En Pascal comme dans dautres langages, on peut dclarer des objets qui ont une dure de vie dlimite par leur porte (variables locales), mais on peut aussi grer des variables dynamiques par lintermdiaire des pointeurs. Lutilisation de variables dynamiques laisse au programmeur la charge de dtruire les variables inutilises, moins que le module dexcution du langage noffre un ramasse-miettes qui dtruise automatiquement les variables devenues inaccessibles. En C++, les objets sont implments par des enregistrements, et le programmeur peut utiliser des objets automatiques ou des pointeurs vers des objets dynamiques. Dans ce cas, lallocation dynamique et la libration de la mmoire pour les objets devenus inutiles ou inaccessibles est sa charge. Les notions de constructeurs et de destructeurs aident cette gestion sans la rendre totalement transparente. linverse, Modula3 et Eiffel assurent eux-mmes la gestion dynamique de la mmoire. Un objet est en ralit un pointeur vers lenregistrement de ses champs. Cette implmentation facilite le travail du programmeur, qui na pas se soucier de la destruction des objets : un algorithme de ramasse-miettes sen occupe pour lui lexcution. Le choix dimplmenter les objets par des pointeurs, et non par des enregistrements comme en C++, offre lavantage de la simplicit. En revanche, laccs tout champ dun objet ncessite une indirection lors de lexcution, ce qui peut tre coteux. De plus, les algorithmes de ramasse-miettes aujourdhui disponibles sont gnralement peu efficaces, et cotent cher en temps et en espace mmoire lors de lexcution.

64

Les langages objets

Bien entendu, ce problme nest pas propre aux langages objets. Nanmoins, on pourrait esprer que le choix du langage nimplique pas le choix de la gestion des objets lexcution. Cest le cas dans Modula3, o lon peut indiquer pour chaque classe si lon souhaite une gestion automatique par ramassemiettes, ou bien une gestion la charge du programmeur. C++ permet galement au programmeur de redfinir la gestion des objets dynamiques au niveau de chaque classe. On peut ainsi utiliser les proprits spcifiques dune classe pour grer la mmoire plus efficacement quavec un ramasse-miettes gnral.

3.7 CONCLUSION
La richesse des langages objets typs est encore loin dtre puise. Les langages actuels souffrent encore du lourd hritage des langages structurs. De nombreux travaux de recherche concernent la smantique des systmes de types mis en uvre dans ces langages, et dcouvrent la complexit des problmes mis en jeu ds lors que lon veut combiner hritage multiple, gnricit, surcharge, etc. Les langages actuels ont dj fait la preuve de leurs qualits : scurit pour le programmeur, facilit de maintenance, rutilisation des classes, efficacit du code excutable. Ils sont de plus en plus facilement adopts dans les entreprises pour le dveloppement de logiciels complexes : systmes dexploitation, environnements de programmation, interfaces graphiques, simulation, etc.

Chapitre 4 SMALLTALK ET SES DRIVS


Nous allons prsenter dans ce chapitre le langage Smalltalk et les langages qui en sont drivs. Ils prsentent la caractristique commune dtre des langages non typs et interprts ou semicompils. La premire version de Smalltalk date de 1972 et fut inspire par les concepts de Simula et les ides dAlan Kay, au laboratoire PARC de Xerox. Aprs une dizaine dannes defforts et plusieurs versions intermdiaires, notamment Smalltalk-72 et Smalltalk-76, Smalltalk-80 reprsente la version la plus rpandue du langage et de la bibliothque de classes qui laccompagne. Smalltalk-80 inclut galement un systme dexploitation et un environnement de programmation graphique. Xerox a aussi cr des machines spcialises pour Smalltalk : le Star et le Dorado. Aujourdhui, Smalltalk est disponible sur stations de travail Unix, sur Apple Macintosh, et sur compatibles IBM PC.

66

Les langages objets

La syntaxe employe dans ce chapitre pour prsenter les exemples est proche de celle de Smalltalk. Les commentaires sont introduits par deux tirets et se poursuivent jusqu la fin de la ligne. Toute instruction est un envoi de message, qui a lune des formes suivantes :
receveur msg1 receveur msg2 argument receveur cl1: arg1 cl2: arg2 cln: argn

La premire forme est un envoi de message unaire (message sans argument), par exemple :
3 factorielle Tableau Nouveau -- calculer 3! -- crer un nouveau tableau

La deuxime forme est un envoi de message binaire (message avec un argument). Les expressions arithmtiques et relationnelles sont exprimes avec des messages binaires :
3+4 a<b -- receveur = 3, msg = +, argument = 4 -- receveur = a, msg = <, argument = b

Enfin la troisime forme est utilise pour des messages n-aires ( un ou plusieurs arguments) et est appele message mots cls. Chaque mot cl se termine par le symbole : et correspond un argument :
tab en: 3 mettre: a

Ici le receveur est tab, le message est en:mettre: et les arguments sont 3 et a. Le nom du message est la concatnation des mots cls (y compris les deux-points). Les noms de message peuvent tre prfixes les uns des autres. Dans ce cas, on prend la plus longue srie de mots cls. Les parenthses permettent de lever les ambiguts ou de forcer lordre dvaluation. titre dexemple, nous utiliserons les messages en: et en:mettre:, pour laccs aux lments de tableau :
tab en: 3 tab en: 4 mettre: a tab en: (tab en: 5) mettre: a -- tab [3] -- tab [4] := a -- tab [tab [5]] := a

Smalltalk et ses drivs

67

La premire expression retourne llment du tableau tab lindice 3. La deuxime affecte a llment dindice 4. La dernire expression affecte a tab [tab [5]]. Dans la ralit, en: et en:mettre: ne sont pas ddis ni rservs laccs aux tableaux, ceux-ci ntant pas un type prdfini de Smalltalk. Les deux autres symboles utiliss dans notre langage sont le symbole daffectation et le symbole de retour de valeur . Nous aurons galement besoin de blocs qui seront dcrits dans la section suivante. Bien que Smalltalk permette de dfinir des classes et des mthodes par envoi de messages, nous utiliserons une forme plus lisible qui sapparente celle offerte par lenvironnement graphique de programmation Smalltalk. La dclaration dune classe aura la forme suivante :
classe superclasse champs mthodes suite de dclarations idClasse idClasse id1 id2 idn de mthodes

Lajout de mthodes dans une classe existante aura une forme identique, en omettant les lignes superclasse et mthodes. Une dclaration de mthode se prsente comme suit :
cl1: arg1 cl2: arg2 cln: argn | idvar1 idvar2 idvarn | corps de la mthode

La premire ligne est le profil de la mthode. Ici, il sagit dune mthode mots cls. La deuxime ligne est optionnelle et permet de dclarer des variables locales. Enfin le corps de la mthode est une suite dexpressions Smalltalk, spares par des points.

68

Les langages objets

4.1 TOUT EST OBJET


Les concepts de base de Smalltalk peuvent se dcrire en quatre axiomes : 1. 2. 3. 4. Toute entit est un objet. Tout objet est linstance dune classe. Toute classe est sous-classe dune autre classe. Tout objet est activ la rception dun message.

Laxiome 2 dfinit la notion dinstanciation. Laxiome 3 dfinit la notion dhritage. Bien distinguer ces deux types de liens (lien dinstanciation est-instance-de et lien dhritage estsous-classe-de) est fondamental la comprhension de Smalltalk. Il dcoule des axiomes 1 et 2 que toute classe est une entit du systme, donc un objet. En tant quobjet, toute classe est donc linstance dune classe, que lon appelle sa mtaclasse. Cette notion de mtaclasse est fondamentale dans Smalltalk, et nous y reviendrons plus loin en dtail. Les seules entits prdfinies dans le langage sont : les nombres entiers, dfinis dans la classe Entier ; la classe Objet qui est la seule ne pas respecter le troisime axiome (Objet nest la sous-classe daucune classe) ; la classe Bloc dtaille ci-dessous ; la mtaclasse Classe. La seule structure de contrle est lenvoi de message aux objets. En particulier, le langage ne contient aucune structure de contrle telles que conditionnelle, boucles, etc. Celles-ci sont dfinies grce la notion de bloc. Un bloc est une instance de la classe prdfinie Bloc. Un bloc contient une liste optionnelle de paramtres et un ensemble dexpressions Smalltalk excutables, spares par des points. Lvaluation dun bloc est obtenue en lui envoyant le message unaire valeur. Les blocs sont nots entre crochets :

Smalltalk et ses drivs

69

incr [ n n + 1 ]. incr valeur.

-- ajoute 1 n

On peut comparer les blocs des procdures anonymes que lon peut excuter par lenvoi du message valeur, ou des lambda-expressions de Lisp. Les blocs peuvent avoir des paramtres. Dans ce cas, le bloc commence par la liste des noms des paramtres, prfixs dun deux-points, et cette liste est spare du corps du bloc par une barre verticale. Le message valeur: prend comme argument la valeur du paramtre rel. Nous utiliserons seulement des blocs avec un paramtre :
ajouter [ :x | n n + x ]. ajouter valeur: 10. -- paramtre = x -- ajouter 10 n

Le langage est donc rduit au strict minimum. De ce point de vue, on peut comparer Smalltalk au langage Lisp. Comme Lisp, Smalltalk est fourni avec un environnement qui vite au programmeur de tout reconstruire dans chaque programme. Cet environnement est un ensemble de classes dintrt gnral telles que boolens, tableaux, chanes de caractres, etc. Il contient galement les classes de lenvironnement de programmation Smalltalk, qui permettent notamment de construire des applications graphiques interactives.

4.2 CLASSES, INSTANCES, MESSAGES


Le deuxime axiome indique que tout objet est linstance dune classe. Prcisons ce que sont les objets et les classes : un objet contient un tat, stock dans un ensemble de champs (appels en Smalltalk variables dinstance). Ces champs sont strictement privs et accessibles seulement par lobjet. Un objet ne peut tre manipul qu travers les messages quon lui envoie. Chaque objet rpond un message en activant une mthode (axiome 4). Les mthodes ne sont pas stockes dans lobjet lui-mme, mais dans sa classe. Le lien dinstanciation qui unit lobjet sa classe est donc crucial : il lui permet de retrouver la mthode activer la rception dun message.

70

Les langages objets

Nouveau

Pile

Pile

Figure 12 - Instanciation dun objet Smalltalk Les mthodes sont stockes dans la classe avec leurs corps, dans le dictionnaire des mthodes. Une classe contient galement la liste des noms des champs de ses instances. Cela lui permet de crer de nouvelles instances : une classe est un objet gnrateur. La cration dun objet, ou instanciation, est ralise en envoyant le message Nouveau une classe. La classe, en tant quobjet, rpond au message Nouveau en crant un nouvel objet (figure 12). Linstanciation, si elle est une opration primitive du langage, est nanmoins ralise par le seul moyen de contrle quest lenvoi de message. Pour rsumer les points prcdents : un objet contient un ensemble de champs ; une classe contient la description des champs de ses instances et le dictionnaire des mthodes que peuvent excuter ses instances ; un objet est cr par lenvoi du message Nouveau sa classe. La correspondance entre un message reu par un objet et la mthode activer se fait simplement sur le nom du message : lobjet recherche dans le dictionnaire des mthodes de sa classe une mthode portant le mme nom que le message. Si une telle mthode existe, elle est excute dans le contexte de lobjet receveur. Le corps de la mthode peut donc accder aux champs de lobjet qui, rappelons-le, lui sont privs.

Smalltalk et ses drivs

71

La rception dun message qui na pas de mthode correspondante dans le dictionnaire des mthodes de sa classe provoque une erreur. Lerreur se manifeste par lenvoi dun message NeComprendsPas: lobjet qui a reu le message incompris, avec pour argument le nom du message incompris. Lobjet a donc lopportunit de rcuprer lerreur : il suffit que sa classe dtienne une mthode de nom NeComprendsPas:. Si ce nest pas le cas, une erreur fatale dexcution est dclenche. Le mcanisme de rponse un message est dynamique et non typ : seule compte la classe de lobjet receveur, qui dtermine le dictionnaire dans lequel la mthode est recherche. Un mme message peut donc donner lieu lexcution de mthodes diffrentes sil est reu par des objets de classes diffrentes. La classe des arguments nintervient pas : si lon veut imposer quun argument dun message appartienne une classe donne, la mthode correspondante doit faire les tests ncessaires. Comme les classes sont des objets, on peut tester si la classe dun objet est gale une classe donne. Laspect dynamique de lenvoi de message apparat lorsque lon modifie au cours de lexcution le dictionnaire des mthodes dune classe. Un message qui tait prcdemment incompris peut tre dfini en cours dexcution. Comme la dfinition de mthode se fait par envoi de message, une mthode peut dfinir dautres mthodes, de la mme faon quune fonction Lisp peut dfinir dautres fonctions. Crer une classe Il est temps de passer un exemple, et de prsenter limplmentation en Smalltalk de la classe Pile. Pour cette classe, nous utilisons la classe Tableau de lenvironnement Smalltalk. Nous utilisons deux messages de cette classe : le message en: qui permet daccder un lment de tableau, le message en:mettre: qui permet de modifier la valeur dun lment de tableau.

72

Les langages objets

classe Pile superclasse Objet champs pile sommet mthodes Initialiser pile Tableau Nouveau. sommet 0. Empiler: unObjet sommet sommet + 1. pile en: sommet mettre: unObjet. Dpiler sommet sommet - 1. Sommet pile en: sommet.

La classe Pile hrite de la classe Objet, qui est une classe prdfinie en Smalltalk. Une pile a deux champs : un tableau qui reprsente la pile, et lindice du sommet de la pile dans le tableau. On peut rfrencer dans un corps de mthode les arguments du message, ainsi que les noms des champs de la classe. Ces noms de champs font rfrence aux champs de lobjet receveur du message. Nous avons dfini quatre mthodes dans la classe Pile : Initialiser initialise les champs de la pile ; le champ pile reoit un objet de la classe Tableau, et le champ sommet est initialis 0. Empiler est un message qui prend un objet en argument (lobjet empiler). Notre pile est htrogne : aucun contrle nest fait sur la classe des objets empils. On peut donc empiler des objets de classes diffrentes. Dpiler enlve le sommet de pile. Sommet retourne le sommet courant. Notre pile nest pas trs sre : il ny a pas de contrle dans Dpiler ni dans Sommet pour sassurer que la pile nest pas vide. Nous pourrons ajouter ces tests lorsque lon aura dcrit la faon de raliser des conditionnelles.

Smalltalk et ses drivs

73

Pour utiliser la classe Pile, il suffit den crer une instance et de lui envoyer des messages :
mapile Pile Nouveau. -- instanciation mapile Initialiser. mapile Empiler: 10. mapile Empiler: 15. mapile Dpiler. s mapile Sommet. -- s contient 10 o UneClasse Nouveau. mapile Empiler: o. mapile Empiler: mapile. -- !!

La dernire instruction, pour surprenante quelle paraisse, est tout fait correcte, puisque lon peut empiler nimporte quel objet.

4.3 HRITAGE
Laxiome 3 indique que toute classe est sous-classe dune autre classe. Cet axiome dtermine larbre dhritage qui lie les classes entre elles. Nous avons vu quune classe prdfinie, Objet, faisait exception cet axiome. Objet est la racine de larbre dhritage, cest--dire que, directement ou indirectement, toute classe est une sous-classe de Objet. Lhritage permet de dfinir une classe partir dune autre, en conservant les proprits de la classe dont on hrite. Une sous-classe est un enrichissement dune classe existante : on peut ajouter de nouveaux champs et de nouvelles mthodes. On peut galement modifier le comportement de la classe de base en redfinissant des mthodes. Lhritage modifie la recherche de mthode que nous avons dcrite dans la section prcdente : lorsquun message est reu par un objet, celui recherche dabord dans le dictionnaire des mthodes de sa classe. Sil ne trouve pas de mthode, il poursuit la recherche dans sa classe de base, et ainsi de suite jusqu trouver une mthode ou bien atteindre la racine de larbre dhritage, savoir la classe Objet.

74

Les langages objets

Objet NeComprendsPas
(Coucou)

6 3 Pile Dpiler b
(NeComprendsPas) (Coucou)

NeComprendsPas: Coucou 4 Dpiler a Coucou 1

Figure 13 - Invocation de mthode. Les flches hachures reprsentent la recherche de mthode. Lenvoi de Dpiler russit, mais lenvoi de Coucou choue Dans le cas o le message na pas de mthode associe, le message NeComprendsPas: est envoy au receveur du message, avec comme argument le nom du message incompris. La recherche dune mthode de nom NeComprendsPas: suit le mme mcanisme : recherche dans la classe de lobjet, puis ses superclasses successives. La classe prdfinie Objet dfinit la mthode NeComprendsPas, de telle sorte que lon est assur de ne pas chouer cette fois-ci (figure 13). Cette technique de traitement des messages incompris permet toute classe de redfinir la mthode NeComprendsPas: et de raliser une rcupration derreur, sans introduire de mcanisme supplmentaire dans le langage tel que la notion dexception.

Smalltalk et ses drivs

75

Dfinir une sous-classe Voyons comment dfinir une classe HPile, sous-classe de Pile, dans laquelle on force les lments appartenir une mme classe. Il sagit dajouter un champ qui stocke la classe des objets que lon empile, ainsi quune mthode qui permet daffecter ce champ. Enfin, il faut redfinir la mthode Empiler afin de raliser le contrle de la classe de lobjet empil. Pour dcrire cette classe, il nous faut introduire la conditionnelle, qui a la forme suivante :
bool siVrai: [ blocSiVrai ] siFaux: [ blocSiFaux ]

Il sagit de lenvoi du message siVrai:siFaux: un objet de la classe Boolen. Les arguments du message sont deux blocs, correspondant aux actions effectuer selon que lobjet receveur est lobjet vrai ou lobjet faux. Nous verrons plus loin comment sont dfinies la classe Boolen et les structures de contrle telles que celle-ci. La dfinition de la classe HPile est la suivante :
classe HPile superclasse Pile champs classe mthodes Classe: uneClasse classe uneClasse. self Initialiser. Empiler: unObjet unObjet Classe = classe siVrai: [ super Empiler: unObjet ] siFaux: [ Empiler: erreur de classe crire ]

La mthode Classe: permet de dfinir la classe des objets que lon met dans la pile. Elle affecte le champ classe et excute self Initialiser. La mthode Empiler: est redfinie de manire tester la classe de lobjet empil. Pour cela, on utilise la mthode Classe, dfinie dans la classe Objet, qui retourne la classe de son receveur. Le receveur du message siVrai:siFaux: est le rsultat

76

Les langages objets

de lexpression unObjet Classe = classe. Cette expression se dcompose en un envoi du message unaire Classe unObjet. Le rsultat est compar au champ classe par le message binaire =. Le bloc excuter si lexpression est vraie, cest--dire si lobjet est de la classe attendue, est super Empiler. Si lexpression est fausse, un message derreur est mis en envoyant le message crire une chane de caractres. Il nous reste dcrire self (utilis dans Classe:) et super (utilis dans Empiler:). Self et Super Nous avons vu que le corps dune mthode sexcute dans le contexte de lobjet receveur du message. lintrieur du corps dune mthode, on a directement accs aux champs de lobjet receveur. En revanche, si lon veut envoyer un message au receveur lui-mme, il faut un moyen de le nommer. On utilise alors la pseudo-variable self, qui dsigne le receveur de la mthode en cours dexcution. Ainsi, dans la mthode Classe: ci-dessus, le receveur du message senvoie lui-mme (self) le message Initialiser. Lorsque lon veut redfinir une mthode dans une sous-classe, on a en gnral besoin dutiliser la mthode de mme nom dfinie dans la classe de base. Pour cela, on utilise une autre pseudo-variable, super, qui dnote lobjet receveur en le considrant comme une instance de sa classe de base (ou superclasse, do le nom de super). Ceci est illustr par la mthode Empiler:. Il sagit de tester une condition (lobjet est-il de la bonne classe ?), et si celle-ci est satisfaite, dempiler effectivement llment. Pour cela, on envoie le message Empiler: super. Si on lenvoyait self, on aurait un appel rcursif, ce qui nest pas leffet souhait. Lenvoi s u p e r signifie que la recherche dune mthode pour le message Empiler commence la superclasse du receveur, et non pas sa classe comme cest le cas normalement (figure 14). Dans la pratique, on utilise super seulement dans le corps dune mthode redfinie, comme nous venons de le faire. Cest en effet la seule situation dans laquelle il est justifi dinvoquer

Smalltalk et ses drivs

77

Empiler 1

Pile Empiler: v [ ]

Hpile 2 Empiler: v [ super Empiler: v ] 3

Figure 14 - Les pseudo-variables self et super limplmentation de la mme mthode dans la classe de base. Utiliser super dans un autre contexte revient transgresser la classe de lobjet receveur. Limplmentation de lenvoi de message Lhritage et la possibilit de modifier dynamiquement les dictionnaires des mthodes impliquent que, pour chaque envoi de message, on effectue lexcution une recherche dans la chane des superclasses de la mthode correspondant au message. Il en rsulte que lenvoi de message est trs coteux. Comme la hirarchie dhritage et les dictionnaires de mthodes changent peu par rapport au nombre de messages envoys, une augmentation des performances importante est obtenue dans la plupart des implmentations en utilisant un cache. Les entres du cache sont constitues de couples <nom de classe, slecteur de message>. Pour chaque entre, le cache contient le rsultat de la recherche du message dans la classe et ses superclasses. Lors dun envoi de message, on cherche dans

78

Les langages objets

le cache une entre correspondant la classe de lobjet receveur du message et au slecteur du message. Si lentre nest pas dans le cache on effectue la recherche dans les dictionnaires, et on entre le rsultat dans le cache. Avec cette technique, on obtient facilement des taux de prsence dans le cache de plus de 98%, et une augmentation importante des performances. Le cache doit tre invalid, cest--dire vid, chaque fois que larbre dhritage ou un dictionnaire de mthode change. Chaque ajout de classe ou de mthode cote donc cher. Aussi, diverses techniques permettent de ne pas invalider tout le cache afin de rduire le cot de ces modifications.

4.4 LES STRUCTURES DE CONTRLE


Un aspect original de Smalltalk est de ne pas contenir de structures de contrle prdfinies. Celles-ci sont dfinies par des classes et des mthodes, laide de la classe prdfinie des blocs, comme nous allons lillustrer ici. Les boolens et la conditionnelle Revenons tout dabord sur la conditionnelle, que nous avons dj utilise. Le receveur du message siVrai:siFaux: est un boolen ; selon sa valeur, cest lun des deux blocs arguments du message qui est excut. Pour produire cela, on dfinit trois classes et deux objets : la classe Boolen, qui na aucune instance ; la classe Vrai, sous-classe de Boolen, qui a une seule instance : lobjet vrai ; la classe Faux, sous-classe de Boolen, qui a une seule instance : lobjet faux. Le receveur de siVrai:siFaux: ne peut tre que lobjet vrai ou lobjet faux. Ainsi, lvaluation de 3 < 4 retourne lobjet vrai, alors que 1 = 0 retourne lobjet faux. Il suffit donc de dfinir la mthode siVrai:siFaux: dans chacune des classes Vrai et Faux :

Smalltalk et ses drivs

79

classe Vrai superclasse Boolen champs mthodes siVrai: blocVrai siFaux: blocFaux blocVrai valeur. classe Faux superclasse Boolen champs mthodes siVrai: blocVrai siFaux: blocFaux blocFaux valeur.

Comme on le voit, si lobjet v r a i reoit un message siVrai:siFaux:, il retourne la valeur du premier argument ; si cest lobjet faux qui reoit le message, il retourne la valeur du deuxime argument. Lhritage nous a permis de reproduire un comportement conditionnel. Cette technique sapplique dautres messages, tels que les oprateurs logiques :
classe Vrai mthodes non faux. ou: unBool vrai. et: unBool unBool. classe Faux mthodes non vrai. ou: unBool unBool. et: unBool faux.

La dfinition des mthodes non, ou: et et: dans les deux classes Vrai et Faux permet dimplmenter facilement les tables de vrit de ces oprateurs. La figure 15 illustre lvaluation dune expression boolenne qui utilise ces oprateurs. Jusquici la classe Boolen ne nous a pas servi : en fait Vrai et Faux auraient trs bien pu hriter directement de Objet. Nous allons maintenant utiliser la classe Boolen pour factoriser des mthodes entre les classes Vrai et Faux :

80

Les langages objets

Vrai non: faux 2

Faux

fau

x
3

ou: unBool unBool 5

fa

ux
6

vrai

faux

1 vrai non

4 ou: faux

faux

Figure 15 - valuation de lexpression (vrai non) ou: faux


classe Boolen superclasse Objet champs mthodes siVrai: unBloc self siVrai: unBloc siFaux: [ ]. siFaux: unBloc self siVrai: [ ] siFaux: unBloc. xor: unBool (self ou: unBool) et: ((self et: unBool) non).

On a dfini dans la classe Boolen deux conditionnelles siVrai: et siFaux:, qui correspondent la conditionnelle siVrai:siFaux: lorsque lon omet lun des blocs. Il est inutile de dfinir ces conditionnelles dans les deux classes Vrai et Faux, comme le montre la figure 16. De faon similaire, le ou exclusif (xor) est dfini partir des oprateurs lmentaires et:, ou: et non, selon lexpression : a xor b = (a ou b) et non (a et b).

Smalltalk et ses drivs

81

Vrai non: faux 2

Faux

fau

x
3

ou: unBool unBool 5

fa

ux
6

vrai

faux

1 vrai non

4 ou: faux

faux

Figure 16 - valuation des conditionnelles Les blocs et les boucles En ce qui concerne les structures de boucles, ce nest plus dans la classe Boolen que vont se faire les dfinitions, mais dans la classe des blocs. Dcrivons tout dabord la boucle tantque :
classe Bloc mthodes tantQueVrai: corps (self valeur) siVrai: [ corps valeur. self tantQueVrai: corps ].

Le message tantQueVrai: sutilise de la faon suivante :


[ x < 10 ] tantQueVrai: [ s s + x ; x x -1 ]

Le receveur est un bloc, car celui-ci doit tre valu chaque tour de boucle, comme le montre limplmentation de la mthode tantQueVrai: : le bloc receveur svalue ; sil est vrai, le corps de la boucle est valu, et litration a lieu grce

82

Les langages objets

lenvoi (rcursif) du message tantQueVrai: au bloc receveur. Comme toujours avec Smalltalk, il ny a aucun contrle de type et rien nempche dcrire :
[ 10 ] tantQueVrai: [ ]. coucou tantQueVrai: [ ].

Selon la valeur retourne par lvaluation du bloc receveur, une erreur aura lieu ou lexcution pourra continuer. Dans le premier cas, lvaluation du bloc receveur retourne la valeur 10, de la classe Entier, qui ne sait rpondre siVrai:. Dans le deuxime cas, le receveur est une chane de caractres, qui ne sait rpondre au message valeur. Mais si lon dfinissait ces mthodes, lexcution pourrait se poursuivre. Le dernier type de structure de contrle que nous allons examiner est litration. Il sagit dexcuter un corps de boucle (un bloc) un certain nombre de fois. Pour reproduire lquivalent de la boucle itrative de Pascal, il faut dfinir une classe Intervalle, qui contient deux entiers reprsentant les bornes infrieure et suprieure de lintervalle. La mthode rpter:, envoye un intervalle, ralise litration :
classe Intervalle superclasse Objet champs inf sup mthodes Inf: i Sup: s inf i. sup s. rpter: corps |i| i inf. [ i < sup ] tantQueVrai: [ corps valeur: i. i i + 1 ].

La mthode Inf:Sup: permet dinitialiser les bornes de lintervalle. La mthode rpter: introduit une variable locale i. Cette variable sert de compteur de boucle, et lon utilise le message tantQueVrai: des blocs pour raliser litration.

Smalltalk et ses drivs

83

Le message rpter: sutilise comme suit :


bornes Intervalle Nouveau. bornes Inf: 10 Sup: 20. s 0. bornes rpter [ :x | s s + x ]. s crire.

-- s = 165

Pour faciliter lcriture de litration, nous allons ajouter un message dans la classe prdfinie Entier :
classe mthodes : val Entier (Intervalle Nouveau) Inf: self Sup: val.

Ce message permet de crer un intervalle en envoyant un entier le message : avec un entier en argument. Le receveur est la borne infrieure de lintervalle, largument la borne suprieure. Lexemple prcdent scrit alors :
s 0. (10 : 20) rpter [ :x | s s + x ]. s crire.

Lexpression 10 : 20 retourne un intervalle auquel on envoie le message ditration rpter. Cette technique ditration sapplique de nombreuses classes. Ainsi, Smalltalk fournit un grand nombre de classes conteneurs pour le stockage dobjets (tableaux, listes, ensembles, dictionnaires, etc.). La plupart de ces classes dfinissent une mthode qui permet litration de leurs lments. Nous pouvons appliquer cela notre classe Pile, en lui ajoutant une mthode rpter: qui value un bloc pour les lments successifs de la pile :
classe Pile mthodes rpter: unBloc (1 : sommet) rpter: [ :i | unBloc valeur: (pile en: i) ].

84

Les langages objets

On utilise un intervalle cr par le message : reprsentant lensemble des indices valides de la pile. Lintervalle est numr par la mthode rpter:. Pour chaque lment de lintervalle, on value le bloc argument avec comme paramtre llment de pile. Pour imprimer le contenu dune pile, il suffit dcrire :
pile Pile Nouveau. pile Initialiser. -- empiler des lments pile rpter: [ :e | e crire ].

Nous avons dfini la mthode rpter: dans la classe Pile laide de la mthode rpter: de la classe Intervalle, cest--dire que lon utilise le polymorphisme ad hoc, intrinsque aux langages objets. Ceci nous permet par exemple de dfinir dans la classe Objet elle-mme une mthode qui imprime le contenu dun objet de la faon suivante :
classe Objet mthodes crireContenu self rpter: [ :e | e crire ].

Tout objet qui sait rpondre rpter: pourra excuter crireContenu :


(10 : 20) crireContenu. pile Pile Nouveau. pile Initialiser. -- empiler des lments pile crireContenu.

Dans cette section, nous avons dfini litration (message rpter:) de la classe Pile en fonction de litration des intervalles. Celle-ci est dfinie en fonction de la rptition des blocs (tantQueVrai:), elle-mme dfinie rcursivement laide de la conditionnelle siVrai:. Enfin cette conditionnelle est dfinie en fonction de la conditionnelle gnrale siVrai:siFaux:, dfinie dans les deux classes Vrai et Faux.

Smalltalk et ses drivs

85

Cet exemple illustre la souplesse et la puissance de Smalltalk, grce lutilisation extensive de la liaison dynamique : il suffit de rajouter des mthodes une classe (comme rpter: dans la classe Pile), pour que les instances de cette classe soient dotes de nouvelles capacits (comme crireContenu). Ceci fait de Smalltalk un environnement idal pour le maquettage et le prototypage dapplications. En revanche, labsence de typage, donc dassurance a priori quun programme ne dclenchera pas dexcution de messages indfinis, est souvent un obstacle la ralisation dapplications finales.

4.5 MTACLASSES
Nous avons vu ds la prsentation des axiomes de base de Smalltalk que toute classe est un objet, et appartient donc une classe, appele mtaclasse. Cette caractristique est la base de nombreuses possibilits intressantes dans Smalltalk. En premier lieu, la notion de mtaclasse permet de raliser linstanciation par un envoi de message : lenvoi du message Nouveau une classe retourne une instance de cette classe. Le message tant envoy une classe, cest dans sa mtaclasse quest cherche la mthode correspondante (figure 17). La mtaclasse ne sert pas seulement linstanciation. En effet, une classe contient la dfinition de ses instances, cest--dire la liste des noms de champs et le dictionnaire des mthodes. On peut donc interroger la classe pour savoir si une mthode particulire est dfinie, pour modifier le dictionnaire des mthodes, et pour dfinir de nouvelles sous-classes. Une mtaclasse dfinit pour cela les mthodes Connat:, Superclasse et DriveDe:. Le message Connat: permet de savoir si une classe sait rpondre au message dont le nom est pass en argument. Le message Superclasse retourne la classe de base de la classe receveur, et enfin le message DriveDe: permet de tester si la classe receveur est une classe drive de la classe dont le nom est pass en paramtre. Voici quelques exemples dutilisation de ces messages :

86

Les langages objets

Classe Nouveau

Classe

Pile

Pile

1 Nouveau

Figure 17 - Cration dun objet : utilisation de la mtaclasse


Pile Connat: Empiler:. Pile Connat: SiVrai:. Vrai Superclasse. Vrai DriveDe: Pile. HPile DriveDe: Objet. -- vrai -- faux -- Boolen -- faux -- vrai

Le corps des mthodes correspondant ces messages se trouve dans le dictionnaire des mthodes de la mtaclasse de leur receveur, comme le prouvent les exemples suivants :
Pile Connat: Nouveau. (Pile Classe) Connat: Nouveau. -- faux -- vrai

Dans le cas de Smalltalk, plusieurs modles de mtaclasses ont t expriments. Le plus simple comprenait une seule mtaclasse dans le systme, nomme Classe. Dans les versions suivantes de Smalltalk, cela sest avr tre une limitation, car on ne pouvait diffrencier les mtaclasses de classes distinctes. Le modle de Smalltalk-80 dfinit une mtaclasse par classe, et la hirarchie dhritage des mtaclasses suit celle des classes. Chaque classe est lunique instance de sa mtaclasse. Pour

Smalltalk et ses drivs

87

MtaClasse Classe

Objet

ClasseTour Pile

ClassePile Tour

Figure 18 - Le modle des mtaclasses de Smalltalk-80 lutilisateur, les mtaclasses sont transparentes car elles sont cres automatiquement par la mthode de dfinition de classe. Par convention, nous appellerons ClasseX la mtaclasse de la classe X. La mtaclasse de Objet est donc ClasseObjet, celle de Pile est ClassePile. Comme Pile hrite de Objet, ClassePile hrite de ClasseObjet. Les mtaclasses hritent de Classe, qui elle-mme hrite de Objet. Le mcanisme des mtaclasses induit une rgression linfini. En effet, une mtaclasse est aussi un objet, qui a donc une classe, une mtaclasse, etc. Comme dans le cas de la hirarchie dhritage, cette rgression est artificiellement interrompue par un bouclage dans le chanage des mtaclasses : dans Smalltalk80, toutes les mtaclasses hritent de la classe Classe, et sont des instances de MtaClasse, selon le schma de la figure 18.

88

Les langages objets

Du point de vue du programmeur, cet artifice est de peu dimportance. Il assure au langage la mta-circularit, cest-dire la capacit se dcrire lui-mme. Grce aux mtaclasses on peut crire un interprte Smalltalk en Smalltalk. Lintrt des mtaclasses pour le programmeur Pour le programmeur, les mtaclasses permettent dune part de dfinir des mthodes de classe, et dautre part de partager des champs entre toutes les instances dune classe. Les mthodes de classe sont des mthodes dfinies dans une mtaclasse, et qui sont utilises lorsque lon envoie des messages une classe. Lutilisation la plus rpandue des mthodes de classe est la redfinition de la mthode dinstanciation Nouveau, et la dfinition dautres mthodes dinstanciation prenant des paramtres. On peut rapprocher cela des constructeurs de certains langages objets typs (voir chapitre 3, section 3.6). Reprenons le cas de la classe Pile. Nous avons dfini dans cette classe la mthode Initialiser qui permet dinstancier et dinitialiser les champs de la pile. Lors de lutilisation de la classe Pile, il faut sassurer dinitialiser chaque pile aprs lavoir instancie avec Nouveau :
pile Pile Nouveau. pile Initialiser.

Si lon oublie linitialisation, la pile ne pourra pas fonctionner comme prvu. Il serait plus sr dassurer linitialisation lors de linstanciation. Il suffit pour cela de redfinir la mthode Nouveau, de la faon suivante :
classe ClassePile mthodes Nouveau | pile | pile super Nouveau. pile Initialiser. pile.

Smalltalk et ses drivs

89

ClassePile Nouveau 3 ClasseTour Nouveau Nouveau 1 Initialiser 4 2

Pile Initialiser 6

Tour

Figure 19 - Hritage des mthodes de classes Cette mthode est dfinie dans la mtaclasse de Pile, puisque cest la classe Pile qui recevra le message Nouveau. Cette mthode commence par instancier la pile en senvoyant le message Nouveau. Nouveau va donc tre envoy la mtaclasse ClassePile, considre comme instance de sa classe de base ClasseObjet. Cela va invoquer la mthode Nouveau dfinie pour tous les objets dans la mtaclasse Classe. La pile rsultante est ensuite initialise : le message Initialiser est envoy la pile, cest donc la mthode Initialiser que nous avons crite dans la classe Pile qui va tre invoque. Enfin la pile initialise est retourne. On aurait pu condenser le corps de la mthode en :
super Nouveau Initialiser.

Dans le corps de cette mthode, on na pas accs aux champs de lobjet pile. Il est donc impossible dinitialiser ces champs autrement que par lenvoi dun message la pile. Dautre part, comme on a redfini la mthode Nouveau, toute classe qui hrite de Pile utilisera galement cette mthode redfinie, grce au paralllisme entre lhritage des classes et

90

Les langages objets

celui des mtaclasses. Par exemple, linstanciation de la classe HPile assurera son initialisation, comme le montre la figure 19. Les mtaclasses permettent galement de dfinir des mthodes dinstanciation avec paramtres. Dans lexemple suivant, on dfinit une mthode dinstanciation qui permet de donner la taille de la pile sa cration :
classe Pile mthodes Initialiser: taille pile Tableau Nouveau: taille. sommet 0. classe ClassePile mthodes Nouveau: taille super Nouveau Initialiser: taille.

On ajoute la classe Pile une mthode Initialiser: qui prend la taille de la pile ; cet argument est transmis la mthode Nouveau: de la classe Tableau. On dfinit ensuite la mthode Nouveau: dans la mtaclasse de Pile, qui instancie une pile et linitialise avec la taille donne. Cette mthode Nouveau: est un message binaire quil ne faut pas confondre avec le message unaire Nouveau que nous avons utilis jusqu prsent. Une fois dfinie cette mthode de classe, lutilisation dune pile devient :
pile Pile Nouveau: 100. pile Empiler: 10.

Selon le mme mcanisme, on pourrait dfinir une mthode dinstanciation pour la classe HPile qui prenne en paramtre la classe des objets de la pile homogne. Variables de classe Lautre utilisation des mtaclasses concerne la possibilit de dfinir de nouveaux champs dans une classe. De fait, ces champs sont accessibles par toutes les instances dune classe

Smalltalk et ses drivs

91

(toute instance connat sa classe) et jouent le rle de variables globales dune classe. Pour illustrer cela, nous allons crer une classe dobjets dont les instances ont un numro unique. La mtaclasse a un champ qui est incrment chaque instanciation :
classe Dmo superclasse Objet champs numro mthodes Initialiser: n numro n. Numro numro. classe ClasseDmo champs nb mthodes Nouveau nb nb + 1. (super Nouveau) Initialiser: nb.

La classe Dmo contient un champ stockant le numro unique de linstance, une mthode dinitialisation, et une mthode qui retourne le numro de lobjet. On ajoute la mtaclasse de Dmo une variable de classe nb, et on redfinit la mthode dinstanciation Nouveau. Cette mthode incrmente la variable de classe nb, puis instancie lobjet et linitialise avec la valeur de nb.

4.6 LES DRIVS DE SMALLTALK


Smalltalk, influenc par Lisp, est lorigine dune famille de langages objets implments au-dessus de Lisp. Il savre en effet que limplmentation des mcanismes des langages objets en Lisp est relativement aise, et fournit un terrain dexprimentation de nouveaux concepts.

92

Les langages objets

Parmi les plus anciennes extensions de Lisp avec des objets, on trouve Flavors, qui a servi implmenter le systme dexploitation des machines Symbolics au dbut des annes 80. Plus rcemment, CLOS (Common Lisp Object System) a repris et tendu le modle des Flavors dans le but de dfinir une norme de Lisp objets. Ceyx et ObjVLisp reprsentent lcole franaise : Ceyx a t dvelopp vers 1985 au-dessus de Le_Lisp par Jean-Marie Hullot, et ObjVLisp, un peu plus ancien, est lobjet des travaux de Pierre Cointe partir de VLisp, un dialecte de Lisp dvelopp par Patrick Greussay lUniversit de Vincennes. Tous ces langages procdent de principes similaires : il sagit dextensions de Lisp, cest--dire que le programmeur utilise des fonctions Lisp pour crire ses programmes2 . Leffet nest pas toujours heureux car le style fonctionnel est assez radicalement diffrent du style de la programmation par objets. Ces langages fournissent en gnral trois fonctions de base : la cration dune nouvelle classe, la cration dune instance, et lenvoi dun message un objet. La fonction de cration dune classe permet de spcifier son hritage (simple ou multiple), ses variables dinstances, ses mthodes, et ventuellement sa mtaclasse. En gnral, le systme est capable de gnrer automatiquement des fonctions daccs aux variables dinstance. En effet, celles-ci sont stockes dans une liste associe latome qui reprsente lobjet, et elles ne peuvent tre accdes autrement que par une fonction. Cet artifice est nanmoins pratiquement transparent pour lutilisateur, comme le montre lexemple ci-dessous :
(def-classe Pile (Objet) (pile sommet)) -- classe, superclasse -- variables dinstance

La suite de cette section ncessite en consquence quelques notions lmentaires de Lisp. Nous esprons cependant ne pas trop ennuyer le lecteur peu familier avec Lisp en limitant les exemples.

Smalltalk et ses drivs

93

(def-mthode (Empiler Pile) (objet) -- mthode, classe, arguments (setq sommet (+ sommet 1)) (envoi mettre pile sommet objet))

Dans cet exemple, dont la syntaxe est inspire de Flavors, defclasse dfinit une nouvelle classe, et def-mthode une nouvelle mthode dans une classe existante. Le corps de la mthode Empiler est constitu de deux expressions : laffectation (setq en Lisp) du champ sommet et lenvoi du message mettre au champ pile. Ce message est suppos stocker lobjet pass en second argument lindice pass en premier argument. Lenvoi de message est une fonction qui sinvoque de la manire suivante :
(envoi message receveur arg1 arg2 argn)

On peut retrouver une syntaxe plus proche de celle de Smalltalk en transformant chaque objet en une fonction, ce que font certains langages. Lenvoi de message scrit alors :
(receveur message arg1 arg2 argn)

La cration dune pile et lempilement dun lment se font de la manire suivante :


(setq mapile (instancier Pile)) (envoi Initialiser mapile) (envoi Empiler mapile 10)

La fonction instancier ralise linstanciation dune classe. La mthode Initialiser est dfinie comme suit :
(def-mthode (Initialiser Pile) () -- mthode, classe, arguments (setq pile (instancier Tableau)) (setq sommet 0))

Lintrt dutiliser Lisp comme langage de base est de disposer dun environnement dj important qui simplifie limplmentation des mcanismes dobjets. La souplesse de Lisp permet galement dexprimenter facilement de nouvelles fonctionnalits et des extensions au modle gnral des objets.

94

Les langages objets

Les dmons des Flavors Ainsi les Flavors fournissent des mcanismes sophistiqus pour lhritage multiple. On a vu que lhritage multiple provoquait des conflits de noms lorsque plusieurs classes de base dfinissent une mthode de mme nom. Les Flavors permettent de dterminer, pour chaque classe, lordre de parcours du graphe des superclasses pour dterminer la bonne mthode invoquer. Il est galement possible de combiner lensemble des mthodes de mme nom hrites, cest--dire de les invoquer lune aprs lautre. Enfin, on peut dfinir des dmons, qui sont des mthodes spciales associes aux mthodes ordinaires. Lorsque la mthode ordinaire est invoque, tous les dmons qui lui sont associs dans la classe ou dans ses superclasses sont galement automatiquement invoqus, selon un ordre que le programmeur peut contrler. Par exemple, pour tracer les invocations du message Empiler, il suffit dajouter le dmon suivant :
(def-dmon (Empiler Pile) (objet) -- mthode, classe, arguments (print "on Empile " objet))

Mme si lon redfinit Empiler dans une sous-classe de Pile, le dmon sera appel. Dans la pratique, la combinaison de mthodes et les dmons sont des mcanismes extrmement puissants, qui sont par l mme difficile matriser : lenvoi dun message un objet peut dclencher une quantit deffets dont lorigine risque dtre difficile identifier. De la mme faon, une modification locale du systme, comme lajout ou le retrait dun dmon, peut provoquer des effets rapidement incontrlables. Les programmeurs Lisp sont coutumiers de ce genre de phnomnes, et trouveront dans ces langages un terrain dexprimentation encore plus vaste. Les mtaclasses dObjVLisp Nous terminerons cette revue des drivs de Lisp par ObjVLisp et son modle de mtaclasses. En effet, ce langage

Smalltalk et ses drivs

95

Classe

Objet

ClassePile

Pile unePile

Tour uneTour

Figure 20 - Le modle de mtaclasses de ObjVLisp offre ce que lon peut considrer comme le modle la fois le plus simple et le plus ouvert de mtaclasses (figure 20). Les classes Classe et Objet sont les deux classes primitives du systme. Objet est une instance de Classe, et Classe est une sousclasse de Objet. Objet na pas de superclasse, alors que Classe est sa propre instance. Objet est la racine de larbre dhritage, tandis que Classe est la racine de larbre dinstanciation. Toute classe doit donc hriter indirectement de Objet, et tre linstance de Classe ou dune classe drive de Classe. Ce dernier point correspond la cration de mtaclasses, sans les contraintes imposes par Smalltalk-80. Dans la figure 20, Pile et Tour ont la mme mtaclasse alors quavec Smalltalk-80, chacune delles aurait sa propre mtaclasse. Dans cet exemple, il est utile de navoir quune mtaclasse car la mme mthode dinstanciation convient aux deux classes. Dun autre ct, le modle de Smalltalk-80 permet de rendre les classes transparentes lutilisateur grce la bijection entre classes et mtaclasses, ce qui nest pas le cas dObjVLisp.

96

Les langages objets

4.7 CONCLUSION
Smalltalk est sans conteste le langage qui est lorigine du succs du modle des objets. Trs rapidement, le langage a t valid par la ralisation dapplications importantes, en particulier lenvironnement de programmation Smalltalk. De leur ct, les extensions de Lisp par les objets ont permis dexprimenter et de mieux comprendre les mcanismes des langages objets. La puissance de Smalltalk est aussi sa principale faiblesse : avec la liaison dynamique et labsence de typage statique, il est impossible de sassurer de la correction dun programme avant son excution, ni mme aprs. De plus, labsence de mcanisme de protection des accs (toute mthode est accessible par tout le monde) najoute pas la scurit de programmation. Des extensions de Smalltalk ont introduit avec succs la dclaration des classes des arguments et des variables locales. Dans CLOS, les types des arguments des mthodes doivent tre dclars. Dans les deux cas, la perte de fonctionnalit est minime, et, dans CLOS, le typage permet mme dintroduire des mthodes gnriques et daugmenter ainsi la puissance du langage. Lautre faiblesse de Smalltalk et des langages interprts en gnral est le manque defficacit lexcution. L encore, de nombreux travaux ont permis daugmenter les performances de faon spectaculaire. Des mesures ont mme montr que, pour certaines applications, la diffrence de performance entre Smalltalk et un langage objets typ et compil ntait pas significative. Dans dautres cas, la diffrence est rdhibitoire, ce qui ne fait que confirmer quil nexiste pas de langage universel. Smalltalk en tout cas reste un langage privilgi pour dcouvrir les concepts des langages objets, mais aussi pour dvelopper des prototypes et, dans certains cas, des applications finales.

Chapitre 5 PROTOTYPES ET ACTEURS


Ce chapitre prsente deux variations importantes des ides de base des langages objets. Les langages de prototypes font disparatre la diffrence entre classes et instances en introduisant la notion unique dobjet prototype. Ils remplacent la notion dhritage par celle de dlgation. Les langages dacteurs gnralisent la notion denvoi de message pour ladapter la programmation parallle : lenvoi de message nest plus une invocation de mthode, mais une requte envoye un objet.

5.1 LANGAGES DE PROTOTYPES


Les langages objets que nous avons prsents jusqu prsent taient tous fonds sur les notions de classe, dinstance et dhritage. Ces trois notions induisent deux relations entre les entits du langage : la relation dinstanciation entre un objet et sa classe, et la relation dhritage entre une classe et sa classe de base. Pour distinguer ces langages objets classiques des

98

Les langages objets

langages de prototypes, nous appellerons les premiers langages de classes. Les premiers travaux sur les langages de prototypes datent de 1986 ; ils sont dus Henry Lieberman du MIT, qui la mme poque a aussi travaill sur les langages dacteurs. Le langage qui a inspir cette prsentation sappelle Self, cr et dvelopp depuis 1987 par David Ungar et Randall Smith luniversit de Stanford. Il reprsente ltape la plus avance dans le domaine des langages de prototypes. Prototypes et clonage Dans un langage de prototypes, il ny a pas de diffrence entre classes et instances : tout objet est un prototype qui peut servir de modle pour crer dautres objets. Lopration qui permet de crer un nouvel objet partir dun prototype sappelle le clonage, et consiste recopier lobjet clon. Dans un prototype, ltat et le comportement sont confondus : il ny a pas de diffrence entre champs et mthodes, appels indistinctement cases ( slots en anglais). Pour accder au champ x, un prototype senvoie le message x. Pour modifier le champ x, il senvoie le message x: avec la nouvelle valeur en argument. Nous dclarons un prototype par une liste de couples nom de case / valeur, entoure daccolades. Les champs ont pour valeur une expression tandis que les mthodes ont pour valeur un bloc (not entre crochets, comme en Smalltalk). Un prototype de pile aura ainsi laspect suivant :
Pile { pile sommet Empiler: unObjet Dpiler Sommet } Tableau cloner. 0. [ sommet: (sommet + 1). pile en: sommet mettre: unObjet ]. [ sommet: (sommet - 1) ]. [ pile en: sommet ].

Prototypes et acteurs

99

Pile pile sommet 0 Initialiser Dpiler Sommet

P1
10 pile sommet 1 Initialiser Dpiler Sommet

P2
10 20 pile sommet 2 Initialiser Dpiler Sommet

Figure 21 - Le prototype Pile et deux clones Les cases pile et sommet sont des champs. Leurs valeurs correspondent aux valeurs initiales la cration du prototype. Les messages daccs pile et s o m m e t , et les messages daffectation pile: et sommet: sont implicitement crs. Les autres cases sont des mthodes. Comme tous les accs aux champs se font par messages, la pseudo-variable self est le receveur implicite des messages. La mthode Dpiler pourrait scrire sous la forme :
Dpiler [ self sommet: (self sommet - 1) ].

Dans cet exemple, Pile est un prototype, donc un objet directement utilisable. Nous allons utiliser Pile comme un modle pour crer et manipuler deux piles (figure 21) :
p1 Pile cloner. p1 Empiler: 10. x p1 Sommet. p2 p1 cloner. p2 Empiler: 20.

-- x vaut 10 -- p2 contient dj 10

Dans un langage de classes, une classe contient une description de ses instances. Dans un langage de prototypes, tout objet est un exemplaire qui peut tre reproduit par clonage. Comme on le voit dans lexemple ci-dessus, ce mcanisme simplifie linitialisation des objets : il suffit dinitialiser correctement le prototype qui sert de modle. titre de comparaison, Smalltalk exige de redfinir la mthode

100 Les langages objets

dinstanciation dfinie dans la mtaclasse ; avec les langages objets typs, il faut introduire des mcanismes spcifiques tels que les constructeurs de C++. La dlgation Le clonage consiste en la duplication exacte dun prototype dans son tat courant. Cela signifie que ltat et le comportement sont copis et quil ny a pas de lien entre lobjet qui sert de modle et lobjet rsultant du clonage, comme illustr dans la figure 21. Il ny a donc pas de possibilit de partage entre les objets. Dans les langages de classes, le partage existe deux niveaux : par le lien dinstanciation entre un objet et sa classe, toutes les instances dune classe partagent le mme comportement, dcrit dans la classe. par le lien dhritage entre une classe et sa superclasse, les classes drives partagent les descriptions contenues dans leurs superclasses. Dans les langages de prototypes, un mcanisme unique, la dlgation, permet des objets de partager des informations. Un objet peut dlguer un autre objet, appel parent, les messages quil ne comprend pas. Dans lexemple de la pile, nous allons partager les mthodes Empiler, Dpiler et Sommet en les mettant dans un prototype part, qui deviendra le parent de tous les clones de la pile. Lorsque lun de ces messages sera envoy un clone de la pile, il sera dlgu son prototype, dans ce cas protoPile.
protoPile { Empiler: unObjet Dpiler Sommet } [ sommet: (sommet + 1). pile en: sommet mettre: unObjet ]. [ sommet: (sommet - 1) ]. [ pile en: sommet ].

Le prototype de la pile scrit maintenant comme suit :

Prototypes et acteurs 101

protoPile Empiler Dpiler Sommet

Pile pile sommet 0

P1 pile 10 sommet 1

P2 pile 10 20 sommet 2

Figure 22 - Partage de mthodes avec les prototypes. Les flches noires indiquent le parent (qui est un champ). Les flches blanches indiquent les oprations de clonage
Pile { parent pile sommet }

protoPile. Tableau cloner. 0.

Lorsque lon clone un prototype, les objets crs ont le mme parent que leur prototype. Dans notre exemple, les clones de Pile ont pour parent protoPile. Lexemple dutilisation vu plus haut est toujours valable, mais le schma des objets lexcution change, comme le montre la figure 22. Lorsque lon envoie le message Empiler p1, il est dlgu son parent protoPile. Dans cet exemple, le couple (Pile, protoPile) joue le rle dune classe dans un langage de classes. Le lien dinstanciation entre un objet et sa classe est ralis par la dlgation. Linstanciation se fait par clonage, mais on pourrait aisment

102 Les langages objets

simuler le mcanisme des langages de classe en dfinissant dans protoPile la mthode Nouveau, qui aurait pour valeur
[ Pile cloner].

Cet exemple montre galement pourquoi laccs aux champs se fait par message : dans les corps des mthodes de protoPile, on fait rfrence des champs (sommet, pile) qui sont dclars dans Pile, donc inconnus de protoPile. Dans la suite, nous utiliserons le terme classe pour parler dun prototype qui contient exclusivement des mthodes. Il faut nanmoins garder lesprit quune classe nest pas une notion distincte dans les langages de protoypes. Simuler lhritage avec la dlgation Nous allons maintenant illustrer lutilisation de la dlgation pour simuler lhritage. Reprenons pour cela lexemple des Tours de Hanoi. Une tour est une pile qui doit contrler la taille des objets empils. On cre donc un prototype Tour qui a pour parent le prototype protoTour, qui a lui-mme pour parent protoPile.
protoTour { parent protoPile. Empiler: unObjet [ unObjet < Sommet siVrai: [ parent Empiler: unObjet ] siFaux: [ Empiler: objet trop grand crire ] ]. } Tour (Pile cloner) parent: protoTour.

Nous avons dfini dans ce nouveau prototype une mthode Empiler: qui, selon la taille de lobjet, demande son parent de raliser lempilement ou affiche un message derreur. Le prototype Tour est cr par clonage du prototype Pile, en changeant son parent. Lutilisation de Tour est illustre par lexemple suivant et la figure 23.

Prototypes et acteurs 103

protoPile Empiler Dpiler Sommet

Pile pile sommet

protoTour Empiler

Tour pile sommet

t pile sommet

Figure 23 - Simulation de lhritage par la dlgation. Les flches ont la mme signification que pour la figure 22
t Tour cloner. t Empiler: 10. t Empiler: 20.

-- Empiler: objet trop grand

Lexemple de la pile que nous venons de prsenter permet de faire le parallle entre les langages de prototypes et les langages de classes. Mais lintrt des prototypes est dtendre les possibilits des langages de classes. Les prototypes permettent ainsi, entre autres, de crer des objets dots de comportements exceptionnels, davoir des champs calculs, et de faire de lhritage dynamique. Nous allons maintenant illustrer ces diffrentes possibilits. Comportements exceptionnels Dans lexemple de la tour, si notre application utilise une seule tour, on peut crer un objet qui a le comportement dune tour sans pour autant crer la classe correspondante. Il suffit de redfinir la case Empiler: dans lobjet lui-mme :

104 Les langages objets

t { parent protoPile. pile Tableau cloner. sommet 0. Empiler: unObjet [ unObjet < Sommet siVrai: [ parent Empiler: unObjet ] siFaux: [ Empiler: objet trop grand crire ] ]. } t Empiler: 10. t Empiler: 20.

-- Empiler: objet trop grand

Cet exemple montre comment crer des objets avec des comportements exceptionnels. Les objets vrai et faux de Smalltalk sont un autre exemple dapplication : l o nous avions cr deux classes Vrai et Faux, avec chacune une instance unique, il nous suffit de crer deux prototypes vrai et faux, contenant chacun une version de la mthode siVrai:siFaux:. On peut noter que de tels objets peuvent tre clons, les clones disposant galement du comportement exceptionnel. Une autre application des comportements exceptionnels est la mise au point de programme. Si lon veut suivre le comportement dun objet prcis, on peut dfinir une mthode dans lobjet de la faon suivante (nous utilisons la mthode ajoute: qui permet dajouter des cases dans un objet) :
p Pile cloner ajoute: { Empiler: unObjet [ p Empiler crire. parent Empiler: unObjet ] }

Tout empilement sur p provoquera un message. Dans un langage de classes, lajout dune trace dans la mthode Empiler de la classe Pile provoquerait lcriture du message pour tout objet de la classe Pile.

Prototypes et acteurs 105

Champs calculs Laccs aux champs dun prototype par envoi de message permet de dfinir des champs dont la valeur est calcule et non pas stocke dans lobjet. Pour illustrer cela, dfinissons un prototype p r o t o P o i n t qui contient des mthodes de manipulation de points, et deux prototypes de points : lun dont la position est stocke en coordonnes cartsiennes (Cartsien), lautre en coordonnes polaires (Polaire) :
protoPoint { crire [ x crire. , crire. y crire ]. +: p [ cloner x: (x + p x) y: (y + p y) ]. }

La mthode +: cre un nouveau point dont les coordonnes sont la somme des coordonnes du receveur et de largument.
Cartsien parent x y } { protoPoint. 0. 0. Polaire { parent protoPoint. rho 0. thta 0. }

Malheureusement, le prototype Polaire est inutilisable car les mthodes de protoPoint utilisent les champs x et y. Pour remdier cette situation, il suffit dajouter les mthodes x, y, x: et y: Polaire pour simuler les champs absents, grce aux quations suivantes :
x = cos y = sin = (x2 + y2) = atan (y / x)

De lextrieur, tout ce passe comme si Polaire avait les champs x et y , qui sont en ralit calculs partir des coordonnes polaires stockes dans lobjet.
Polaire { parent protoPoint. rho 0. thta 0.

106 Les langages objets

x y x: val y: val }

[ rho * thta cos ]. [ rho * thta sin ]. [ rho: (val * val + y * y) sqrt. thta: (y / val) atan ]. [ rho: (x * x + val * val) sqrt. thta: (val / x) atan ].

On pourrait de faon similaire ajouter les champs calculs rho et thta au prototype Cartsien. Cela permettrait dutiliser les coordonnes les plus adquates dans les mthodes de protoPoint. Hritage dynamique Le parent dun prototype est une case similaire aux autres, lexception de son rle particulier pour la dlgation lors de lenvoi de messages. Rien ninterdit donc de modifier la valeur de la case qui contient le parent dun objet aprs la cration de celui-ci : cest lhritage dynamique. Ainsi, en utilisant les dfinitions de P i l e et de T o u r vues plus haut, on peut transformer une tour en pile en affectant sa case parent :
t Tour cloner. t Empiler: 10. t Empiler: 20. t parent: Pile. t Empiler: 20.

-- Empiler: objet trop grand -- la tour devient une pile -- OK

Les applications de cette technique sont multiples. Par exemple, il arrive que la classe dun objet ne puisse tre dtermine compltement sa cration, ou quelle soit amene changer lors de la vie de lobjet. Lhritage dynamique permet de prciser la classe au fur et mesure des connaissances acquises. Par exemple, dans un systme graphique, des oprations entre objets graphiques permettent de crer de nouveaux objets. Si un objet ainsi cr se trouve tre un objet rgulier comme un rectangle, il peut changer de parent pour utiliser des mthodes plus efficaces que celles dfinies sur un objet quelconque. Inversement, si un rectangle est transform par une rotation, il devient un polygone, et doit changer de parent en consquence.

Prototypes et acteurs 107

Lhritage dynamique est galement utile lors de la mise au point dun programme : pour observer un objet, on lui affecte comme parent un prototype qui trace les oprations effectues. On peut galement tester une nouvelle implmentation dune classe en affectant, en cours dexcution, la nouvelle classe au parent dun objet. Conclusion En abolissant les diffrences entre classe et instance, et en unifiant les champs et les mthodes, les langages de prototypes ouvrent de nouvelles portes aux langages objets liaison dynamique. De faon assez surprenante, limplmentation dun langage de prototypes peut tre plus efficace que celle dun langage tel que Smalltalk. Ceci ncessite nanmoins la mise en uvre de techniques assez complexes, qui consistent pour lessentiel garder dans des caches les rsultats des recherches de mthodes pour optimiser les envois de messages, et compiler diffrentes versions dune mthode pour des contextes dappels diffrents. Il nen reste pas moins que les langages de prototypes sont des langages non typs, donc sans aucun contrle de la validit dun programme avant son excution. Autant il est envisageable dajouter des dclarations de type dans un langage comme Smalltalk, autant cela est illusoire dans un langage de prototypes. En effet, la notion de type, qui correspond celle de classe dans un langage de classes, na pas vraiment dquivalent dans un langage de prototypes. Si lon considre que le type dun objet est son parent, tout typage statique est impossible car le type de lobjet peut changer durant sa vie. Par ailleurs, la structure dun objet peut aussi changer par ajout de cases, de telle sorte quutiliser la structure dun objet comme type est galement impossible. En labsence de moyens de vrification statique des programmes, les langages de prototypes restent donc rservs essentiellement au prototypage

108 Les langages objets

5.2 LANGAGES DACTEURS


Les langages dacteurs sont ns au MIT des travaux de Carl Hewitt dans les annes 70 avec le langage Plasma. Au dbut des annes 80 Henry Liebermann, du MIT, a dvelopp ACT1, puis Akinori Yonezawa de lInstitut de Technologie de Tokyo a introduit ABCL/1. Lobjet des langages dacteurs est de fournir un modle de calcul parallle fond sur des entits indpendantes et autonomes communiquant par messages. Ces entits, appeles acteurs, sont composes dun tat et dun filtre. Ltat est constitu de variables locales et de rfrences dautres acteurs, tandis que le filtre est une suite de modles de messages auxquels lacteur peut rpondre. Chaque acteur est autonome : lorsquun message arrive, il vrifie sil correspond un modle de son filtre. Si cest le cas, lacteur receveur excute le bloc dinstructions correspondant. Sinon, lacteur dlgue le message un autre acteur, appel son mandataire ( proxy en anglais). Ce mcanisme de dlgation est similaire celui des langages de prototypes, et nous ne reviendrons pas dessus. Les seules actions que peut effectuer un acteur sont lenvoi de message, la cration de nouveaux acteurs, et sa transformation en un autre acteur. Lenvoi de message est asynchrone : lacteur ne se soucie pas de ce quil advient des messages quil envoie. Cet envoi asynchrone introduit le paralllisme de manire naturelle : lacteur continue son activit pendant que le message envoy est trait par son destinataire. Comme chaque acteur est squentiel, il dispose dune bote aux lettres dans laquelle sont stocks les messages qui arrivent pendant quil traite un message. La cration dacteur est simple : un acteur peut crer un autre acteur, dont il spcifie le mandataire, ltat, et le filtre. La transformation dun acteur est similaire sa cration, la diffrence que le nouvel acteur remplace le prcdent, cest-dire quil rcupre sa bote aux lettres. Dans le modle introduit par Gul Agha, un acteur peut se transformer avant davoir

Prototypes et acteurs 109

termin le traitement du message en cours. Dans ce cas, un acteur peut traiter plusieurs messages en parallle : il lui suffit, lorsquil reoit un message, de commencer par spcifier son remplaant. Celui-ci pourra immdiatement traiter le prochain message en attente. La possibilit de traiter des messages en parallle interdit de modifier ltat de lacteur. Ceci justifie la ncessit de fournir explicitement un remplaant, qui est gnralement une copie modifie de lacteur initial. Lenvoi asynchrone de messages, sil introduit naturellement le paralllisme, pose un problme : comment envoyer un message et obtenir un rsultat en retour ? La rponse consiste transmettre une continuation avec le message. Une continuation dsigne lacteur auquel le receveur dun message devra transmettre sa rponse. Un acteur peut recevoir la rponse dun message en se mentionnant comme continuation du message, mais il peut aussi mentionner un autre acteur qui saura traiter le rsultat mieux que lui. Considrons par exemple les trois acteurs suivants : le lecteur lit des expressions au clavier, l v a l u a t e u r value des expressions, et limprimeur affiche des valeurs. Le lecteur, lorsquil a lu une expression, envoie un message lvaluateur avec comme contenu lexpression valuer et comme continuation limprimeur. Ainsi, lvaluateur transmettra directement le rsultat afficher limprimeur. Dans un modle classique, le lecteur demanderait lvaluation lvaluateur, recevrait la rponse, et la transmettrait limprimeur. Avec cet exemple, on pourrait penser que la continuation est inutile, puisque lvaluateur envoie toujours sa rponse limprimeur. Ce nest pas le cas : lvaluateur peut, si lexpression est complexe, senvoyer des messages avec des sous-expressions valuer, en se spcifiant comme sa propre continuation. Un autre acteur, qui lit par exemple dans un fichier, peut envoyer des messages lvaluateur avec comme continuation un imprimeur qui crit dans un fichier de sortie (figure 24).

110 Les langages objets

val: expr Imprimeur

val: expr self

impr: rsultat

Evaluateur

Lecteur

Imprimeur

fichier

fichier

Figure 24 - Envois de messages avec continuations Programmer avec des acteurs Pour les exemples de cette section, nous avons adapt la syntaxe utilise prcdemment pour les prototypes. Un acteur est dcrit par son nom, ventuellement suivi de son tat et de son filtre. Le filtre est un ensemble de couples <modle de message / action>. Un modle est un nom de message, avec des arguments et une continuation ventuelle, indique par une flche . La cration dacteurs et lenvoi de message ont la forme suivante :
crer acteur (tat1, tat2, tatn) acteur msg1: arg1 msg2: arg2 msgn: argn continuation

La figure 25 montre la reprsentation dun acteur : la flche horizontale reprsente la vie de lacteur, les lignes brises reprsentent les envois de messages et les lignes pointilles reprsentent les crations dacteurs. Lorsquun acteur reoit un message, les modles de son filtre sont compars au message reu. Le modle qui ressemble le plus au message reu est choisi, et laction correspondante est

Prototypes et acteurs 111

rception de message acteur tat envoi de message cration d'acteur vie de l'acteur

Figure 25 - Reprsentation dun acteur active. Si aucun modle ne convient, le message est transmis au mandataire, sil y en a un ; sinon il y a erreur. Programmer avec des acteurs exige doublier tout ce que lon sait de la programmation pour apprendre de nouvelles techniques spcifiques du paralllisme. Prenons lexemple simple de la factorielle, que chacun sait crire sous la forme dune fonction rcursive. Voici comment on ralise le calcul dune factorielle avec des acteurs :
acteur factorielle filtre fact: 0 r [ r envoie: 1]. fact: i r [ cont crer mult (i, r). self fact: (i - 1) cont]. acteur mult tat val rec filtre envoie: v [ rec envoie: (v * val)].

Lacteur factorielle a deux modles de messages, qui concernent tous les deux le message fact: avec une continuation. Le premier modle ne reconnat le message fact: que lorsque son argument est nul ; le second reconnat les autres messages fact:. Lacteur factorielle utilise un autre acteur, mult, pour laider faire son calcul. Ltat de mult est constitu par un entier val et un acteur rec. Lorsquil reoit le message envoie:, il

112 Les langages objets

fact: 3 res fact: 2 mult1 fact: 1 mult2 fact: 0 mult3

factorielle
envoie: 1

mult3
1, mult2

mult2
2, mult1

envoie: 1

envoie: 2

mult1
3, res1 envoie: 6

res

Figure 26 - Calcul de la factorielle renvoie le message envoie: lacteur rec, avec comme argument le produit de val et de la valeur reue. En dautres termes, mult ralise une multiplication et transmet le rsultat un acteur qui a t spcifi sa cration. Lorsque lacteur factorielle reoit un message lui demandant de calculer la factorielle de i et de transmettre le rsultat la continuation r, il commence par crer un acteur mult. Cet acteur multipliera par i la valeur qui lui sera transmise, et enverra le rsultat r. Ensuite, lacteur factorielle senvoie un message lui demandant de calculer la factorielle de i-1 et de transmettre le rsultat lacteur m u l t quil vient de crer. Cet acteur multipliera donc la factorielle de i-1 par i, produisant le rsultat escompt. Lorsque factorielle doit calculer la factorielle de 0, il envoie directement 1 la continuation du message, ce qui

Prototypes et acteurs 113

fact: 3 r1

fact: 4 r2

factorielle

24

Figure 27 - Calcul simultan de plusieurs factorielles termine le calcul en vitant lenvoi infini de messages de factorielle lui-mme. La figure 26 visualise le calcul dune factorielle. Il y a cration dun ensemble dacteurs mult i, un pour chaque tape du calcul. Ces acteurs sont inactifs jusqu rception du message envoie:. Le calcul se dclenche en chane lorsque factorielle envoie le message envoie: mult3. Le modle des acteurs nous a oblig transformer la rcursion en un ensemble dacteurs qui reprsente le droulement du calcul. Dans cet exemple, le calcul est strictement squentiel. Nanmoins, lacteur factorielle est capable de calculer plusieurs factorielles simultanment. En effet, observons ce qui se passe lorsquil reoit deux messages fact: (figure 27) : les deux calculs senchevtrent sans se mlanger, car les messages transportent linformation suffisante pour le calcul quils sont en train deffectuer, sous la forme de continuations. Il existe dautres techniques de programmation avec des acteurs, que nous ne dtaillerons pas ici, part la jointure de continuations dont nous donnerons un exemple plus loin.

114 Les langages objets

Envoi de messages Les langages dacteurs apportent aux langages objets une nouvelle vision de lenvoi de message. En fait, il ny a que dans les langages dacteurs que le terme denvoi de message est correct : dans les autres langages, il sagit dinvocation de procdures ou fonctions. La communication asynchrone, que nous avons utilise jusqu prsent, nest pas la seule disponible dans les langages dacteurs. ABCL/1, par exemple, dispose de deux autres types de communication. Le premier, la communication synchrone, correspond lenvoi de message avec attente de rponse. Dans ce cas la continuation est obligatoirement lmetteur du message. La communication synchrone bloque lmetteur jusqu rception du message. Lautre type de communication dABCL/1, la communication anticipe, permet de lever cette contrainte. Au lieu dtre bloqu dans lattente de la rponse, lacteur continue fonctionner. Pour savoir si la rponse est disponible, il interroge le receveur du message. De cette faon, un acteur peut lancer des messages et collecter les rponses lorsquil en a besoin. Par ailleurs ABCL/1 offre deux modes de transmission des messages : le mode ordinaire, dans lequel les messages sont ajouts dans la bote aux lettres du receveur, et le mode express. Lorsquun message express est envoy un acteur, celui-ci est interrompu pour traiter ce message immdiatement. Sil tait dj en train de traiter un message express, le message reu est mis dans la bote aux lettres des messages express, qui est toujours traite avant la bote aux lettres des messages ordinaires. Les diffrents types de communication comme les modes de transmissions sont destins faciliter la programmation avec des acteurs qui reste pourtant assez droutante. Ainsi les messages express permettent de raliser des interruptions. Supposons quun acteur reprsente un tableau, et quun message permette de trouver un lment dans le tableau. Lacteur dcide de

Prototypes et acteurs 115

rpartir le travail entre plusieurs acteurs qui cherchent dans des parties disjointes du tableau. Ds quun acteur a trouv llment cherch, il est inutile pour les autres de poursuivre. Lacteur principal peut alors les interrompre par lenvoi dun message express. Sans ce moyen, chaque acteur devrait dcouper sa recherche en tapes lmentaires, en senvoyant des messages lui-mme afin que le message dinterruption puisse tre pris en compte. Objets et acteurs On peut se demander dans quelle mesure les langages dacteurs sont des langages objets. Un acteur est un objet dans le sens o il dtient un tat et un comportement, mais, contrairement un objet, il est actif et sapparente plutt un processus. Cela apparat clairement dans lexemple de la factorielle : lacteur est un objet qui calcule une factorielle, alors quun langage objets classique verrait la factorielle comme un message envoy un nombre. Les acteurs sont donc aptes reprsenter un comportement ou un calcul, linstar des fonctions, ce que les objets sont incapables de faire. Mais les acteurs peuvent aussi reprsenter un tat et des mthodes associes, linstar des objets. De tels acteurs peuvent tre amens crer des acteurs de calcul pour rpondre un message. Lexemple ci-dessous illustre cet aspect. Il sagit de reprsenter le jeu des Tours de Hanoi, et de le rsoudre. Nous aurons besoin dans cet exemple de listes de type Lisp. Une liste est note entre accolades. Nous supposons quune liste rpond aux messages synchrones ajoute:, car et cdr, et quelle peut tre parcourue par le message rpter: qui prend en argument un bloc avec un argument. Nous dnotons les messages synchrones en utilisant le symbole comme continuation. Les messages synchrones peuvent retourner une valeur en utilisant loprateur . Nous allons utiliser un acteur Tour pour reprsenter une tour, dont ltat contient la pile des disques. Un acteur H a n o i reprsentera lensemble du jeu.

116 Les langages objets

acteur Tour tat pile sommet filtre Empiler: x Dpiler Sommet

[ pile en: sommet mettre: x . sommet sommet + 1 ] [ sommet sommet - 1 ] [ pile en: sommet ]

Lacteur Tour est ici une pile : nous navons pas insr le test qui compare la taille de lobjet empil avec le sommet courant. Ceci nest pas important dans cet exemple, car la rsolution par programme des Tours de Hanoi assure que les rgles du jeu sont respectes. Sommet est un message synchrone qui retourne lobjet en sommet de pile. Nous avons suppos que lacteur qui reprsente la pile sait rpondre aux messages daccs en: et en:mettre:. Lempilement utilise un message synchrone.
acteur Hanoi tat gauche droite centre nd filtre Initialiser [ (1 nd) rpter [ :i | gauche Empiler: (nd - i) ] ] dplacer: dp vers: arr [ arr Empiler: (dp Sommet ). dp Dpiler ] Jouer [ jh crer JoueHanoi (self). jh dplacer: nd de: gauche vers: droite par: centre tag: 1 jh ]

Lacteur Hanoi reprsente les Tours de Hanoi. Initialiser permet de mettre nd disques de tailles dcroissantes sur la tour de gauche. dplacer:vers: dplace un disque de la tour passe en premier argument vers celle passe en second argument ; il envoie le message synchrone Sommet la tour de dpart, empile la valeur retourne sur la pile darrive, et dpile la tour de dpart. Jouer permet de lancer la rsolution du jeu. Jouer cre un acteur JoueHanoi charg de rsoudre le jeu. Rappelons lalgorithme squentiel rcursif qui rsout les Tours de Hanoi :

Prototypes et acteurs 117

-- dplacer n disques de la tour dp vers la tour arr -- en utilisant la tour intermdiaire inter procdure Hanoi (n : entier; dp, arr, par : tour) { si n 0 alors { Hanoi (n-1, dp, inter, arr); DplaceDisque (dp, arr); Hanoi (n-1, inter, arr, dp); } }

Avec les acteurs, la difficult provient de la rsolution parallle qui doit nanmoins produire une liste o r d o n n e de dplacements de disques. Selon la technique utilise pour calculer la factorielle, et laide dune jointure de continuations, nous allons crer des acteurs qui reprsentent les tapes du calcul, cest--dire les sous-squences de dplacements de disques.
acteur JoueHanoi tat hanoi filtre dplacer: 1 de: D vers: A par: M tag: t cont [ cont tag: t liste: {D A} ] dplacer: n de: D vers: A par: M tag: t cont [ c crer Jointure (cont, t, {D A}, 0). self dplacer: n-1 de: D vers: M par: A tag: t*2 c. self dplacer: n-1 de: M vers: A par: D tag: t*2+1 c. ] tag: t liste: listedpl [ listedpl rpter: [ :d | hanoi dplacer: (d car ) vers: (d cdr ) ] ]

Lorsquil reoit le message de rsolution dplacer:de:vers: par:tag:, lacteur JoueHanoi cre un acteur Jointure pour la jointure des continuations et senvoie deux messages de rsolution pour les deux sous-problmes. Lacteur de jointure est initialis avec le dplacement mdian et la continuation de JoueHanoi. Il attend de recevoir les deux listes de dplacements,

118 Les langages objets

les combine avec le dplacement mdian, et envoie le rsultat la continuation. Voyons maintenant lacteur de jointure :
acteur Jointure tat cont t listedpl attente filtre tag: t*2 liste: l [ listedpl l ajoute: listedpl . self envoyer ] tag: t*2 +1 liste: l [ listedpl listedpl ajoute: l . self envoyer ] envoyer [ attente attente+1. (attente = 2) siVrai: [ cont tag: t liste: listedpl ] ]

Afin de combiner les listes de dplacements correctement, lacteur de jointure doit pouvoir distinguer les deux listes quil reoit. Pour cela, les messages de rsolution transportent une tiquette, appele tag, qui numrote chaque rsolution de faon unique : la rsolution dtiquette n dclenche deux rsolutions dtiquettes 2*n et 2*n+1. Lacteur de jointure est initialis avec ltiquette de la rsolution pour laquelle il a t cr ; il peut donc dterminer lorigine des listes quil reoit et combiner les dplacements en consquence. Le message e n v o y e r permet denvoyer le dplacement final la continuation, lorsque les deux sous-listes ont t reues. Le message tag:liste: est envoy par JoueHanoi lorsquil a un seul disque dplacer, et par Jointure lorsquil a combin les listes avec le dplacement mdian. Il est reu soit par Jointure, auquel cas il contient les sous-listes de dplacements, soit par JoueHanoi lui-mme lorsque la rsolution est termine. Dans ce cas, la valeur de ltiquette nest plus utile. Lorsque JoueHanoi reoit la rsolution finale, il numre la liste des dplacements et envoie H a n o i des messages de dplacement de disque dplace:vers:. Ces envois de messages sont synchrones pour assurer leur traitement dans lordre dmission ; sinon, tout le travail prcdent aurait t inutile. Lexemple ci-dessous initialise un acteur Hanoi avec deux disques et lance une rsolution, qui est illustre figure 28.

Prototypes et acteurs 119

Jouer

tours
G, C, D, 2 dplacer: 2 de: G vers: D par: C tag: 1 jh dplacer: 1 de: C vers: D par: G tag: 3 c dplacer: G vers: C dplacer: G vers: D dplacer: C vers: D

Hanoi

dplacer: 1 de: G vers: C par: D tag: 2 c

jh tours JoueHanoi c
tours, 1, {GD}, 0 tag: 2 liste: {GC} tag: 3 liste: {CD} tag: 1 liste: {GC}{GD}{CD}

Jointure
envoyer envoyer

Figure 28 - Rsolution des Tours de Hanoi


tours crer Hanoi ( crer Tour (crer Tableau, 0), crer Tour (crer Tableau, 0), crer Tour (crer Tableau, 0), 2). tours Initialiser. tours Jouer.

-- gauche -- centre -- droite -- nd

120 Les langages objets

Le degr de paralllisme obtenu est assez faible : lacteur de jointure fonctionne en parallle avec lacteur JoueHanoi, mais cest ce dernier qui fait lessentiel du travail. Si lon a n disques, il y a cration dun seul acteur JoueHanoi, et de 2 n-1 -1 acteurs de jointure. Le temps de rsolution est exponentiel en fonction de n, car lacteur JoueHanoi senvoie 2n-1 messages quil traite squentiellement. On pourrait augmenter le paralllisme en crant un acteur JoueHanoi chaque dcomposition du problme. Pour n disques, il y aurait cration de 2*(n-1) acteurs JoueHanoi et de 2 n-1 -1 acteurs de jointure, et le temps de rsolution serait linaire en fonction de n. Conclusion La programmation avec un langage dacteurs nest pas simple, mais cela est vrai de tous les langages parallles. Par contre, le modle des acteurs est facile comprendre, ce qui nest pas le cas de tous les modles du paralllisme. Les langages dacteurs sont plus proches des langages de prototypes que des langages de classes : ils utilisent la dlgation, et la cration dacteurs est proche du clonage. Cette ressemblance a des raisons historiques : la plupart des langages dacteurs ont t dvelopps au-dessus de Lisp, et ils sont contemporains des premiers langages de prototypes. Il en rsulte que les langages dacteurs ne sont pas typs, quils nont pas de mcanismes de modularit, et quils emploient la liaison dynamique. Les travaux sur les langages dacteurs se sont focaliss essentiellement sur la smantique de lenvoi de message, au dtriment de ces autres aspects. Les langages dacteurs et les langages de prototypes explorent des directions indpendantes qui se dmarquent du modle strict des langages de classe. Ils permettent aussi de mieux comprendre lessence de la programmation par objets, et lon peut sattendre des retombes de ces travaux sur les langages objets plus classiques.

Chapitre 6 PROGRAMMER AVEC DES OBJETS


Ce chapitre prsente quelques techniques usuelles de programmation par objets et une bauche de mthodologie. Il se termine par lexemple complet des Tours de Hanoi, qui complte les classes Pile et Tour qui nous ont servi tout au long de ce livre. Bien que ce chapitre sapplique aux langages objets typs aussi bien quaux langages non typs, lexemple sera dvelopp dans le langage que nous avons utilis au chapitre 3. Un certain nombre de points seront donc spcifiques des langages typs. Contrairement la programmation imprative ou fonctionnelle classique, la programmation par objets est centre sur les structures de donnes manipules par le programme. Le dveloppement dun programme suit donc les trois phases suivantes : Identification des classes. Dfinition du protocole des classes, cest--dire les en-ttes des mthodes publiques (visibles de lextrieur des classes).

122 Les langages objets

Dfinition des champs et implmentation des corps des mthodes. Dfinition ventuelle de mthodes prives (visibles uniquement de lintrieur des classes). Lidentification correcte des classes, lutilisation correcte de lhritage et la bonne dfinition des protocoles sont dterminants lorsque lon souhaite crer des classes rutilisables. Aussi est-il important de comprendre et de pratiquer la programmation par objets avant de lutiliser (pour des besoins professionnels), afin den percevoir clairement, par lexprimentation, les limites et les subtilits intrinsques.

6.1 IDENTIFIER LES CLASSES


Lidentification des classes dobjets, qui semble souvent aise, ncessite en ralit une bonne pratique de la programmation par objets. Il faut viter de dfinir trop peu de classes, qui correspondent des fonctionnalits trop complexes, mais aussi de dfinir trop de classes, qui entretiennent des relations complexes entre elles. Le juste milieu est affaire dexprience. Les mthodes de conception pour les langages objets sont encore balbutiantes. Les mthodes de conception par objets ( ne pas confondre avec les prcdentes) sont plus rpandues, mais elles ne sont pas toujours les mieux adaptes aux langages objets. Certaines dentre elles par exemple ne prennent pas en compte lhritage. Une bonne approche consiste aussi tudier les bibliothques de classes fournies avec les langages ou disponibles pour ceux-ci. La bibliothque de classes de Smalltalk est ce titre trs instructive. Elle contient lensemble des classes qui implmentent le systme dexploitation et lenvironnement de programmation graphique du systme Smalltalk. Nous proposons de distinguer plusieurs catgories de classes. Sans tre exhaustive, cette classification donne une ide des diffrents rles que peut jouer une classe dobjets dans une application.

Programmer avec des objets 123

Les classes atomiques reprsentent des objets autonomes, cest--dire dont ltat vu de lextrieur ne dpend pas dautres classes. Par exemple, des classes dobjets graphiques (rectangles, cercles, etc.) ou gomtriques (points, vecteurs, etc.) sont des classes atomiques. La classe des piles nest pas une classe atomique car une pile renferme des objets auxquels on peut accder. Les classes composes sont des classes dont les instances sont des assemblages de composants, ceux-ci tant accessibles de lextrieur. Accessible signifie que lon peut avoir connaissance des composants, mme si la classe contrle ou limite leur accs. Par exemple, laccs aux composants peut tre en lecture seule, ou bien par lintermdiaire de noms symboliques (indice, chane de caractres). La classe des Tours de Hanoi est un exemple de classe compose ; dans lexemple que nous donnons plus loin dans ce chapitre, nous verrons que laccs aux tours se fait de manire symbolique, par un type numr. Les classes conteneurs sont un cas particulier de classes composes. Une instance dune classe conteneur (un conteneur) renferme une collection dobjets et fournit des mthodes pour ajouter, enlever, rechercher des objets de la collection. Souvent, une classe conteneur fournit galement un moyen dnumrer les objets de la collection, souvent par lintermdiaire dune classe active ou dun itrateur (voir ci-dessous). La classe des piles est un exemple typique de classe conteneur. Les classes actives sont des classes qui reprsentent un processus plutt quun tat. En Smalltalk, les blocs sont des classes actives, qui nous ont servi dans le chapitre 4 dfinir des structures de contrle. Un autre exemple courant de classe active sont les classes ditrateurs. Un itrateur est un objet qui permet dnumrer les composants dun autre objet. En gnral, lobjet itr est un conteneur. Une mthode de litrateur retourne le prochain objet de lobjet numr. Litrateur sert stocker ltat courant de lnumration, ce qui permet plusieurs itrateurs dtre actifs simultanment sur le mme objet. Un exemple ditrateur est prsent plus loin dans cette section.

124 Les langages objets

Les classes abstraites sont des classes qui ne sont pas prvues pour tre instancies, mais seulement pour servir de racine une hirarchie dhritage. En gnral, les classes abstraites nont pas de champs. Leurs mthodes doivent tre redfinies dans les classes drives, ou doivent appeler de telles mthodes. Une classe abstraite sert dfinir un protocole gnral qui ne prjuge pas de limplmentation des classes drives. Un bon gage dextensibilit dun ensemble de classes est dinsrer des classes abstraites en des points stratgiques de larbre dhritage. Par exemple, la classe abstraite Collection dcrite ci-dessous contient des mthodes dajout, de retrait, et de recherche dun lment. Ces mthodes doivent tre virtuelles si le langage impose la dclaration explicite de la liaison dynamique, comme en C++. Les classes drives (Ensemble, Liste, Fichier, etc.), doivent redfinir ces mthodes en fonction de leur implmentation de la collection.
Collection = classe { mthodes procdure Ajouter (Objet); procdure Retirer (Objet); fonction Chercher (Objet) : boolen; fonction Suivant (Objet) : Objet; }

Une classe abstraite peut galement contenir des mthodes dont le corps est dfini dans la classe abstraite, comme la mthode AjouterSiAbsent ci-dessous :
procdure AjouterSiAbsent (o : Objet) { si non Chercher (o) alors Ajouter (o); }

En imposant un protocole sur ses classes drives, une classe abstraite permet dobtenir une plus grande homognit entre les classes. Par exemple, la classe Collection vite davoir une mthode Ajouter dans Ensemble et une mthode Insrer dans Liste : les deux mthodes devront sappeler Ajouter.

Programmer avec des objets 125

Une classe abstraite sert galement dfinir des classes gnrales ( dfaut de gnriques) : soit une classe abstraite A et une classe quelconque C ; en dclarant dans C des champs ou des arguments de mthodes de type A, on pourra utiliser C avec une plus grande gamme dobjets que si lon avait utilis une classe concrte. titre dexemple, et partir de la classe Collection dfinie ci-dessus, on peut construire une classe gnrale Itrateur, alors quen labsence de classe abstraite, on serait contraint de dfinir une classe ditrateurs pour chaque classe conteneur.
Itrateur = classe { champs coll : Collection; courant : Objet; mthodes procdure Initialiser (c : Collection) { c := coll; courant := coll.Suivant (NUL); } fonction Suivant () : Objet { o : Objet; o := courant; si o NUL alors courant := coll.Suivant (courant); retourner o; } }

Nous avons suppos ici que Collection.Suivant(NUL) retourne le premier objet de la collection, et que Collection.Suivant(o) retourne NUL lorsque o est le dernier lment de la collection. NUL est un objet distingu qui sert ici simplifier lcriture. Hritage ou imbrication ? Le principal problme dans lidentification des classes est le choix de la hirarchie dhritage. Il sagit de dterminer si une classe doit hriter dune autre, et si oui de laquelle. Ici, les langages typs sont plus contraignants que les langages non typs car le choix de lhritage dterminera ce que lon peut

126 Les langages objets

faire des instances de la classe. Dans lexemple de la classe Collection ci-dessus, si lon dfinit une classe qui nhrite pas de Collection mais qui dfinit la mthode Suivant, on ne pourra pas utiliser la classe Itrateur sur les objets de cette classe. Ce serait possible dans un langage non typ, car tout ce que demande la classe Itrateur lobjet itr est de rpondre au message S u i v a n t . En consquence, le choix de larbre dhritage est la fois plus difficile et plus dterminant dans les langages objets typs. Lhritage est un mcanisme puissant, ce qui signifie quil peut tre utilis dans diffrents contextes. Lhritage peut servir reprsenter la spcialisation et lenrichissement : cest ce pour quoi il est utilis le plus souvent. Ainsi, dans les chapitres prcdents, nous avons fait hriter la classe Tour de la classe Pile car une tour est une pile spciale . Par contre, nous nous sommes gards de faire hriter Pile dune hypothtique classe Tableau, et nous avons prfr mettre le tableau dans la pile. La relation dordre entre les classes qui est induite par lhritage doit nous inciter utiliser lhritage lorsque les protocoles des classes sont compatibles, et nous en dissuader lorsque seulement les structures des classes sont compatibles. Le protocole dune classe est compatible avec celui dune autre classe sil est inclus dans celui-ci. De mme, la structure dune classe est compatible avec celle dune autre classe si elle est incluse dans celle-ci. Cest la compatibilit des protocoles qui permet dutiliser lhritage non seulement pour la spcialisation (cas dgalit), mais aussi pour lenrichissement. Une pile et une tour ont le mme protocole : empiler, dpiler, lire le sommet. Par contre un tableau a un protocole qui permet daccder un lment quelconque, ce qui est incompatible avec le protocole des piles. La smantique de lhritage est telle quune classe drive doit avoir un protocole compatible, mais aussi une structure compatible, puisque les champs de la classe de base sont hrits. Cette contrainte est une source de problmes lorsque lon dfinit la hirarchie des classes. En effet, le choix de la

Programmer avec des objets 127

hirarchie, qui est initialement guid uniquement par la compatibilit des protocoles, peut tre invalid plus tard cause dune incompatibilit de structure. La solution consiste en gnral crer des classes abstraites dont les classes de structures incompatibles sont des sous-classes. Considrons par exemple la classe Polygone, avec comme mthodes le dessin, la rotation et la translation. La classe Rectangle, qui reprsente des rectangles dont les cts sont horizontaux et verticaux, est une candidate pour lhritage, car son protocole est compatible. Mais lon peut, pour des raisons defficacit, vouloir reprsenter le rectangle par deux points diagonaux alors que le polygone ncessite une liste de points. Lhritage devient impossible, et lon doit introduire une classe abstraite Forme comme suit :
Forme = classe { mthodes procdure Dessiner (f : Fentre); procdure Rotation (centre : Point; angle : rel); procdure Translation (v : Vecteur); } Polygone = classe Forme { champs points : Liste [Point]; mthodes -- idem Forme } Rectangle = classe Forme { champs p1, p2 : Point; mthodes -- idem Forme

Le mme phnomne se reproduit si lon veut dfinir une classe Carr. Celle-ci devrait en toute logique hriter de Rectangle, mais si lon veut reprsenter un carr par un point et une dimension, il faut dfinir une classe Quadrilatre, sousclasse de Forme, dont Rectangle et Carr sont des sous-classes. Cela conduit alourdir inutilement la hirarchie dhritage. Les utilisations de lhritage autres que la spcialisation et lenrichissement sont gnralement voues sinon lchec, du moins des solutions de compromis. Lutilisation de lhritage

128 Les langages objets

entre classes de structures compatibles mais de protocoles incompatibles, comme Tableau et Pile, peut tre acceptable si le langage permet de masquer la relation dhritage du point de vue de linclusion de types. Cest le cas par exemple en C++ avec lhritage priv. Dans les autres cas, il vaut mieux y renoncer, mme si cela alourdit la programmation. Hritage multiple Lhritage multiple est source de nombreux problmes. Nous avons dj voqu les conflits de noms et lhritage rpt. Mais lhritage multiple pose aussi des problmes dordre smantique et mthodologique. Selon notre approche de compatibilit de protocoles, on peut dcider que lhritage multiple est justifi si la sous-classe a un protocole compatible avec chacune de ses superclasses. Des conflits de protocoles peuvent apparatre si une partie du protocole de la sous-classe est incluse dans les protocoles de plus dune de ses superclasses : nous avons vu au chapitre 3 lexemple de la mthode crire dans le cas de la classe TourGM hritant de Tour et Fentre. Dans ce cas, il faut imprativement redfinir dans la sous-classe la partie du protocole qui cre des conflits. Si lhritage multiple est justifi, le protocole redfini devrait faire appel aux protocoles des superclasses. Comme lhritage simple, lhritage multiple impose lhritage de structure des classes parentes. Cet hritage de structure soulve le problme de lhritage rpt : doit-on dupliquer les champs hrits dune mme classe par plusieurs chemins ? Bien que les langages fournissent divers mcanismes de contrle, comme nous lavons vu, il est plus sain de ne pas utiliser lhritage multiple dans une telle situation, car les risques sont grands de rendre la hirarchie des classes inutilisable. Notons toutefois que lhritage rpt ne provoquera pas de conflit dhritage de structure si la classe hrite plusieurs fois est une classe abstraite sans champ : cest la seule situation dans laquelle lhritage rpt est sans risque.

Programmer avec des objets 129

Dans le cas o lhritage multiple nengendre pas de conflit de protocole, et si les classes hrites nont pas danctre commun contenant des champs, alors on peut envisager lutilisation de lhritage multiple. On obtient alors une classe agglomre, proche dune classe compose qui aurait un champ par classe hrite. La diffrence entre classe agglomre et classe compose est quune instance dune classe agglomre est dun type compatible avec chacune des classes dont elle hrite. Chaque classe hrite donne une facette diffrente la classe agglomre, et les conditions que nous avons imposes assurent lindpendance de ces facettes. Le polymorphisme dhritage sur une classe agglomre revient utiliser une instance de cette classe sous lune de ses facettes. La similarit entre agglomration et composition nous indique que, si lon ne souhaite pas mettre en uvre lhritage multiple pour lune des raisons dcrites ci-dessus, on peut lui substituer la composition. On ne dispose plus des facettes et de la facilit de programmation associe, mais on obtient un ensemble de classes plus facile matriser. Si la composition peut remplacer lhritage multiple, lhritage multiple ne doit pas remplacer la composition : ce nest pas parce quune voiture est constitue dun moteur, dune carrosserie et de quatre roues quil faut faire hriter la classe Voiture de la classe Moteur, de la classe Carrosserie et quatre fois de la classe Roue ! Cest le protocole, et non pas la structure, qui dtermine lhritage.

6.2 DFINIR LES MTHODES


Nous venons de voir comment les classes et larbre dhritage sont dfinis. Il est apparu, en particulier, que la notion de protocole tait cruciale dans la dtermination de lhritage. Nous allons maintenant fournir des lments afin daider la dfinition des protocoles, cest--dire des mthodes publiques des classes. Comme pour les classes, nous allons proposer une classification des mthodes.

130 Les langages objets

Les mthodes daccs servent obtenir des informations sur le contenu dun objet, et modifier son tat, sans autre effet de bord. Laccs peut consister simplement retourner ou affecter la valeur dun champ, ou bien effectuer un calcul qui utilise ou modifie la valeur des champs de lobjet. Dans ce dernier cas, on parlera plutt de mthode de calcul. La plupart des langages de la famille Smalltalk engendrent automatiquement une mthode daccs en lecture et une mthode daccs en criture pour chaque champ. Ceci va lencontre de lencapsulation car toute classe est alors compltement expose ses clients. Les mthodes de construction permettent dtablir des relations entre les objets. Les objets doivent en effet se connatre afin de pouvoir senvoyer des messages. Pour cela, les objets stockent des rfrences vers dautres objets, rfrences quil est ncessaire de maintenir. Dterminer les bonnes mthodes de construction est une tche dlicate car les relations entre objets sont souvent complexes. Par exemple, une fentre doit connatre les objets graphiques quelle contient, et un objet graphique doit savoir dans quelle fentre il se trouve. Deux problmes se posent : o mettre la mthode de construction, et comment tablir le lien, ici bidirectionnel, entre les objets. La mthode dajout peut tre dans la classe des fentres ou dans la classe des objets graphiques. On peut aussi dcider de fournir les deux mthodes. Dans tous les cas, la mthode de lune des classes devra faire appel une mthode de lautre classe pour tablir le lien rciproque, comme dans cet exemple :
procdure Fentre.Ajouter (og : OGraphique) { -- ajouter og dans la fentre og.AjoutDans (moi); -- prvenir og }

On voit ici que les deux classes Fentre et O G r a p h i q u e entretiennent un lien privilgi : si une autre classe appelle directement OGraphique.AjoutDans, la relation de rciprocit entre la fentre et lobjet graphique ne sera pas respecte et le systme sera dans un tat erron. La seule solution est de faire

Programmer avec des objets 131

en sorte que seule la classe F e n t r e puisse appeler OGraphique.AjoutDans. Les mcanismes de contrle de visibilit tels que les amis de C++ et les listes dexportation dEiffel permettent de raliser cela. Les mthodes de contrle utilisent le graphe des objets qui rsulte de lapplication des mthodes de construction pour raliser un calcul qui met en jeu plusieurs objets. Une mthode de contrle ne ralise pas de calcul par elle-mme, elle dtermine les objets comptents et leur retransmet toute ou partie du calcul. Par exemple, le raffichage dune fentre est une mthode de contrle qui demande chacun des objets de la fentre de se redessiner. De la mme faon que pour les mthodes de construction, les mthodes de contrle ont souvent besoin de faire appel des mthodes spcifiques des objets qui ne doivent pas tre accessibles par dautres clients. Dans lexemple suivant, la mthode de raffichage dune fentre doit invoquer la mthode prive I n i t D e s s i n de la fentre afin de mettre en place lenvironnement ncessaire pour que les objets puissent se redessiner :
procdure Fentre.Redessiner () { InitDessin (); -- mettre en place lenvironnement pour chaque objet graphique o faire o.Redessiner (); } procdure OGraphique.Redessiner () { -- dessiner lobjet dans sa fentre }

Les mthodes de dessin des objets graphiques doivent donc tre visibles seulement par les classes capables de mettre en place cet environnement avant de les appeler, OGraphique.Redessiner ne doit donc tre visible que de Fentre. Les mthodes de classe sont des mthodes globales une classe. Elles jouent le rle de procdures et fonctions globales, mais bnficient des mmes rgles de visibilit que les mthodes

132 Les langages objets

normales. Dans les langages qui disposent de mtaclasses, les mthodes de classes sont dfinies dans celles-ci ; dans les autres langages, un mcanisme spcifique permet de dclarer des champs et des mthodes de classe. Les mthodes de classe sont utilises pour accder aux champs de classe pour contrler le fonctionnement de lensemble des instances. Nous avons dj vu une utilisation de mthodes de classe pour numroter les instances dune classe. Un autre exemple dutilisation est le contrle, par un champ et des mthodes de classe, du type de traces mises par les mthodes pour laide la mise au point. Dfinir la visibilit des mthodes Les exemples prcdents ont montr limportance de la visibilit des mthodes pour la scurit de la programmation. Les langages non typs noffrent pas en gnral de contrle de visibilit : toute mthode, et mme tout champ (grce aux mthodes daccs cres automatiquement), est visible de toute classe. Au contraire, les langages typs offrent diffrents domaines de visibilit. Cette distinction est rvlatrice des diffrences dans lutilisation des deux familles de langages. Avec un langage typ, on souhaite une encapsulation importante pour assurer une programmation plus sre en effectuant le maximum de contrles de manire statique. Les langages non typs, de leur ct, sont souvent utiliss pour le prototypage, et lon souhaite alors un accs ouvert aux objets afin de faciliter le dveloppement incrmental du prototype. Il est souvent dlicat de dterminer le bon domaine de visibilit de chaque mthode, mme lorsque le contrle de visibilit est sophistiqu, comme dans Eiffel. En particulier, si lon dfinit une classe rutilisable, les diffrentes utilisations de la classe conduiront en gnral modifier linterface, le plus souvent en rendant visible un plus grand nombre de mthodes. Les domaines de visibilit dont on a besoin sont les suivants : le domaine priv la classe, le domaine visible par les sous-classes, les domaines visibles par des classes privilgies, et le domaine public toute classe.

Programmer avec des objets 133

Les domaines visibles par des classes privilgies sont les plus dlicats dfinir, car il faut viter la prolifration des classes privilgies dune classe donne. Dans un systme bien conu, les classes fonctionnent par groupes, et chaque classe a pour classes privilgies les autres classes du groupe. Cela circonscrit les dpendances entre classes et facilite la rutilisation. La double distribution La smantique de lenvoi de message dans les langages objets consiste dterminer la mthode invoque selon la classe de lobjet receveur du message. Il arrive frquemment que le seul receveur ne suffise pas dterminer la bonne mthode, car celle-ci peut dpendre galement de la classe effective des paramtres du message. Dans les langages typs, le polymorphisme dhritage permet en effet de passer comme paramtres effectifs des objets dune sous-classe de la classe dclare pour le paramtre formel. Dans les langages non typs, la classe des paramtres nentre pas en jeu dans la recherche de mthode. Dans les deux cas, la technique de la double distribution ( double-dispatching ) permet de rsoudre le problme. Nous allons lillustrer avec lexemple suivant : deux classes abstraites Afficheur et OGraphique fournissent des mthodes pour la reprsentation dobjets graphiques sur des priphriques. La mthode de dessin dun objet graphique sur un afficheur dpend la fois de la classe de lafficheur et de celle de lobjet graphique. Avec les classes Fentre et Imprimante, la double distribution de la mthode de dessin se ralise comme suit :
Afficheur = classe { mthodes procdure Dessiner (o : OGraphique); } Fentre = classe Afficheur { mthodes procdure Dessiner (o : OGraphique) { o.AfficherFentre (moi); } }

134 Les langages objets

Imprimante = classe Afficheur { mthodes procdure Dessiner (o : OGraphique) { o.AfficherImpr (moi); } }

La mthode Dessiner est redfinie dans chaque classe drive, et appelle une mthode de OGraphique, dont le nom encode la sous-classe mettrice : cest la premire tape de la double distribution. La deuxime tape a lieu dans les sous-classes de OGraphique : chaque mthode daffichage sur un priphrique donn y est redfinie.
OGraphique = classe { mthodes procdure AfficherFentre (aff : Fentre); procdure AfficherImpr (aff : Imprimante); } Rectangle = classe OGraphique { mthodes procdure AfficherFentre (aff : Fentre) { -- dessiner un rectangle dans une fentre } procdure AfficherImpr (aff : Imprimante) { -- dessiner un rectangle sur une imprimante } } Cercle = classe OGraphique { mthodes procdure AfficherFentre (aff : Fentre) { -- dessiner un cercle dans une fentre } procdure AfficherImpr (aff : Imprimante) { -- dessiner un cercle sur une imprimante } }

Programmer avec des objets 135

OGraphique
AfficheFentre AfficheImpr

Afficheur
Dessiner

Rectangle
AfficheFentre AfficheImpr

Cercle
AfficheFentre AfficheImpr

Fentre
Dessiner

Imprimante
Dessiner

Figure 29 - Double distribution La figure 29 illustre le mcanisme : la premire distribution a lieu dans les sous-classes de Afficheur, et la deuxime dans les sous-classes de OGraphique. tant donn un objet graphique et un afficheur, cest finalement lune des mthodes daffichage des sous-classes de OGraphique qui sera appele. Si lon rajoute une sous-classe Afficheur, il faut dfinir la mthode Dessiner dans cette sous-classe ; il faut de plus ajouter la mthode daffichage correspondante dans OGraphique, et une implmentation de cette mthode dans chaque sous-classe de OGraphique. Si lon ajoute une sous-classe OGraphique, il faut implmenter les mthodes daffichage sur chaque priphrique dans cette nouvelle classe. On peut noter que lajout dune nouvelle classe dobjets graphiques peut se faire sans toucher aux classes dafficheurs, tandis que linverse nest pas vrai. Ce critre peut aider choisir dans quel sens doit se faire la double-distribution. Si lon a n sous-classes de Afficheur et p sous-classes de OGraphique, il faut implmenter n mthodes dans chaque sousclasse de OGraphique, soit n*p mthodes. Ceci nest pas surprenant puisque laffichage dpend du type dafficheur et du type dobjet graphique. Mais il faut galement implmenter une mthode de distribution pour chaque sous-classe de Afficheur, soit n mthodes de plus. De par les services quelle rend, la double distribution est dun cot acceptable. Notons

136 Les langages objets

galement que, dans un langage typ qui autorise la surcharge des mthodes (mthodes de mme nom avec des listes de paramtres diffrentes), les mthodes de distribution peuvent porter le mme nom (ici se serait Afficher).

6.3 RUTILISER DES CLASSES


La rutilisation de classes est certainement lun des avantages importants des langages objets. La dfinition de classes rutilisables nen est pas moins un travail difficile. On se trouve confront la dfinition de classes rutilisables lorsque lon conoit une bibliothque, cest--dire un ensemble de classes fournissant un service particulier. Une telle bibliothque contient en gnral des classes utiliser telles quelles, et dautres classes prvues pour tre drives : cest la rutilisation par hritage. La gnricit offre galement un moyen de rutilisation puissant, mais comme elle nest pas disponible dans tous les langages, nous ne lvoquerons pas dans cette partie. Lorsque lon conoit une bibliothque, on a une ide du type de rutilisation qui sera employ. Mais dans la pratique, les classes sont rarement rutilises de la faon que lon avait imagin : les besoins des utilisateurs ne correspondent pas exactement au service offert par la bibliothque, ou bien le mode de rutilisation prvu ne sadapte pas lapplication, ou bien encore les utilisateurs utilisent mal le mode de rutilisation prvu. La puissance dune bibliothque de classes sera dautant plus grande quelle pourra tre utilise de manire non anticipe. Nous allons voir quelques techniques qui permettent datteindre cet objectif. Certaines classes peuvent tre prvues pour tre rutilises directement, mais la plupart du temps, la rutilisation se fait par lintermdiaire de lhritage. Cest notamment le cas pour les classes abstraites. La rutilisation par hritage consiste redfinir des mthodes de la classe de base, et ajouter de nouveaux champs et de nouvelles mthodes. Cest la redfinition qui pose bien sr le plus de problmes. La classe de

Programmer avec des objets 137

base doit dfinir quelles mthodes doivent tre redfinies, celles qui peuvent tre redfinies, et celles qui ne doivent pas ltre. Ces trois types de mthodes dpendent du protocole de la classe de base. Sans cette information, on ne peut pas rutiliser la classe de base correctement. Une technique particulirement sre consiste autoriser seulement la redfinition de mthodes prives, comme le montre lexemple suivant :
PileAbstraite = classe { mthodes prives procdure Ajouter (o : Objet); -- redfinir procdure Retirer (); -- redfinir fonction EstVide () : boolen; -- redfinir fonction Dernier : Objet; -- redfinir procdure PileVide (); -- redfinir mthodes -- mthodes publiques procdure Empiler (o : Objet) { Ajouter (o); } procdure Dpiler () { si non EstVide () alors Retirer (); } fonction Sommet : Objet { si non EstVide () alors retourner Dernier () sinon { PileVide (); retourner NUL; } } }

Parce quil est trs simple, cet exemple est un peu caricatural. Il montre nanmoins que, en interdisant la redfinition des mthodes publiques, on ne peut crer une sous-classe qui ne respecte pas la smantique dune pile. La mthode PileVide permet de redfinir la faon de signaler ou de traiter lerreur qui consiste accder au sommet dune pile vide.

138 Les langages objets

De manire gnrale, le protocole public assure les contrles de manire respecter la smantique de la classe, tandis que le protocole priv dfinit les oprations atomiques dfinir dans chaque sous-classe. Cela nempche pas, le cas chant, de redfinir une mthode du protocole public dans une sous-classe, en particulier pour des raisons defficacit. Classes dpendantes Lutilisation de classes composes ou agglomres conduit en gnral des ensembles de classes qui sont prvus pour fonctionner ensemble. La rutilisation de ces classes doit se faire en les drivant en parallle, ce qui pose des problmes spcifiques. Reprenons lexemple des classes Fentre et OGraphique. Une fentre contient une liste dobjets afficher et un objet graphique contient la fentre dans laquelle il saffiche. La drivation parallle a gnralement pour objectif de dfinir deux nouvelles classes qui, comme leurs classes de base, doivent fonctionner ensemble. Par exemple, on cherche dfinir les classes Fentre3D et OGraphique3D pour laffichage dobjets trois dimensions :
Fentre3D = classe Fentre { mthodes publiques procdure Ajouter (o : OGraphique); } OGraphique3D = classe OGraphique { mthodes prives procdure AjoutDans (f : Fentre); }

-- hrite

-- hrite

Une fentre trois dimensions ne peut contenir que des objets graphiques trois dimensions. Malheureusement, ceci nest pas reflt par les mthodes hrites : la procdure Fentre.Ajouter prend un objet graphique quelconque en paramtre, de mme que la procdure OGraphique.AjoutDans prend une fentre quelconque en argument.

Programmer avec des objets 139

Dans les langages non typs, il est facile de tester la classe effective dun objet, comme nous lavons montr au chapitre 4 avec la classe H P i l e . Par contre, il ny a pas de solution satisfaisante ce problme dans les langages typs. Il faudrait redfinir la mthode Ajouter avec un paramtre de type OGraphique3D. Certains langages, notamment Eiffel, autorisent la redfinition dune mthode dans une sous-classe avec des paramtres dont les types sont inclus dans les types des paramtres correspondants dans la classe de base. Malheureusement, le contrle de type ne peut plus tre ralis statiquement, ce qui fait dEiffel un langage faiblement typ, comme le montre cet exemple :
U = classe { procdure g (); } A = classe { procdure f (p : U) { p.g (); } } V = classe U { procdure h (); } B = classe A { procdure f (p : V) { p.h (); } }

a : A; b : B; u: U; a = b; -- polymorphisme dinclusion a.f (u); -- la liaison dynamique appelle B.f

Dans cet exemple, la mthode f est redfinie dans la classe B, avec un paramtre appartenant une sous-classe de celui dclar pour A.f. Lappel a.f(u) est correct du point de vue du typage statique. lexcution, a contient un objet de classe B donc, par liaison dynamique, cest B.f qui sera appele. Malheureusement, B.f attend un paramtre de type V alors que lon a pass un paramtre de type U. Pour viter une erreur lexcution, le compilateur doit engendrer du code pour contrler le type effectif des objets lexcution. Dans les langages qui noffrent pas le mcanisme dEiffel, la seule solution sre consiste fournir une mthode qui permette de connatre et de tester la classe dun objet. Cela revient

140 Les langages objets

dfinir des objets qui reprsentent des classes, comme les mtaclasses des langages objets de la famille Smalltalk. Dans tous les cas, la drivation parallle oblige abandonner lide dun typage statique, ce qui implique une attention plus grande lors de la conception du systme pour limiter les situations qui font intervenir le contrle de types dynamique.

6.4 EXEMPLE : LES TOURS DE HANOI


Nous prsentons dans cette section lexemple complet des Tours de Hanoi. Nous partons de la classe Tour dfinie dans le chapitre 3, et nous dfinissons la classe Hanoi qui reprsente le jeu des Tours de Hanoi.
TourPos = (gauche, centre, droite); Hanoi = classe { champs tours : tableau [TourPos] de Tour; mthodes procdure Construire (); procdure Initialiser (n : entier); procdure Dplacer (de, vers : TourPos); procdure Jouer (de, vers, par : TourPos; n : entier); }

Le type TourPos sert identifier les trois tours. Hanoi est une classe compose offrant un accs contrl ses composants. Les corps des mthodes sont les suivants :
procdure Hanoi.Construire () { tours [gauche] := allouer (Tour); tours [centre] := allouer (Tour); tours [droite] := allouer (Tour); } procdure Hanoi.Initialiser (n : entier) { tours [gauche] . Initialiser (n); tours [centre] . Vider ();

Programmer avec des objets 141

tours [droite] . Vider (); } procdure Hanoi.Dplacer (de, vers : TourPos) { d : entier; d := tours [de] . Sommet (); si tours [vers] . PeutEmpiler (d) alors { tours [de] . Dpiler (); tours [vers] . Empiler (d); } sinon erreur.crire ("Dplacer : coup impossible"); } procdure Hanoi.Jouer (de, vers, par : TourPos; n : entier) { si n > 0 alors { Jouer (de, par, vers, n -1); Dplacer (de, vers); sortie.crire (de, " -> ", vers); Jouer (par, vers, de, n -1); } }

Les objets erreur et sortie sont des objets globaux qui permettent dafficher des messages lcran. allouer permet de crer un objet dynamiquement (comme le new de Pascal). On peut utiliser le jeu des Tours de Hanoi comme suit :
hanoi : Hanoi; hanoi.Construire (); hanoi.Initialiser (4); hanoi.Dplacer (gauche, centre); hanoi.Dplacer (gauche, droite); hanoi.Dplacer (droite, centre); -> jouer : coup impossible

La rsolution automatique du jeu se fait de la faon suivante :


hanoi.Initialiser (3); hanoi.Jouer (); gauche -> centre -- revenir la position initiale

142 Les langages objets

gauche -> droite centre -> droite

Les Tours de Hanoi graphiques Essayons maintenant dutiliser la classe TourG dfinie au chapitre 3 pour dfinir la classe HanoiG des Tours de Hanoi graphiques. Il nous suffit de redfinir la mthode Construire pour allouer des tours graphiques au lieu de tours normales. Comme les tours graphiques ncessitent une fentre pour laffichage, nous allons ajouter un champ dans le nouvelle classe.
HanoiG = classe Hanoi { champs f : Fentre; mthodes prives procdure ConstruireTour (t : TourPos; x, y : entier); mthodes procdure Construire (); } procdure HanoiG.ConstruireTour (t : TourPos; x, y : entier) { tours [t] := allouer (TourG); tours [t] . Placer (f, x, y); } procdure HanoiG.Construire () { f := allouer (Fentre); ConstruireTour (gauche,10, 100); ConstruireTour (centre, 50, 100); ConstruireTour (droite, 90, 100); }

La mthode ConstruireTour est une mthode auxiliaire de Construire. Elle doit donc tre prive. Lexemple dutilisation de la classe Hanoi sapplique un objet de la classe HanoiG. Toutefois, les dplacements des

Programmer avec des objets 143

disques ne seront pas visualiss graphiquement. Si lon veut ajouter cette animation, il faut redfinir la mthode Dplacer.
procdure HanoiG.Dplacer (de, vers : TourPos) { Hanoi.Dplacer (de, vers); -- animation }

Cette solution nest pas satisfaisante car si le dplacement est invalide, H a n o i . D p l a c e r met un message derreur, et lanimation ne devrait pas avoir lieu. Le seul moyen de tester la validit du dplacement dans HanoiG.Dplacer est de rpter le code de Hanoi.Dplacer, ce qui rend la classe drive HanoiG dpendante de limplmentation de la classe Hanoi. Un meilleure solution consiste modifier la classe Hanoi pour la rendre plus flexible du point de vue de la rutilisation, comme nous lavons illustr avec la classe PileAbstraite de la section 6.3 ci-dessus. Dfinissons pour cela une mthode prive Bouger, appele depuis Dplacer lorsque le dplacement est licite :
Hanoi = classe { mthodes prives procdure Bouger (d : entier; de, vers : TourPos); mthodes procdure Dplacer (de, vers : TourPos); } procdure Hanoi.Dplacer (de, vers : TourPos) { d : entier; d := tours [de] . Sommet (); si tours [vers] . OK (d) alors { tours [de] . Dpiler (); tours [vers] . Empiler (d); Bouger (d, de, vers); -- notifier le dplacement

144 Les langages objets

} sinon erreur.crire ("Dplacer : coup impossible"); } procdure Hanoi.Bouger (d : entier; de, vers : TourPos) { -- rien par dfaut }

La classe HanoiG devient :


HanoiG = classe Hanoi { champs f : Fentre; mthodes prives procdure ConstruireTour (t : TourPos; x, y : entier); procdure Bouger (d : entier; de, vers ; TourPos); mthodes procdure Construire (); } procdure HanoiG.Bouger (d : entier; de, vers : TourPos) { -- animation du disque d de la tour de vers la tour vers }

Il nest plus ncessaire de redfinir Dplacer. On a rendu la classe extensible en dfinissant une mthode prive (ici Bouger) qui notifie les classes drives dun changement dtat significatif. Il faut bien reconnatre que, sans la tentative de drivation, ceci ne serait pas apparu spontanment. Lcriture de classes rutilisables ncessite donc une connaissance a priori des contextes de rutilisation.

6.5 CONCLUSION
Ce chapitre nous a montr les possibilits mais aussi les limites des langages objets. Par rapport aux autres langages, les langages objets favorisent la modularit et la rutilisation, sans

Programmer avec des objets 145

pour autant rsoudre compltement les problmes lis ces aspects. Les exemples de dveloppement dapplications avec un langage objets ont montr que les classes aident matriser de gros systmes, condition de les spcifier soigneusement, et de sadapter au langage en faisant des concessions au modle idal des objets. En dautres termes, les langages objets sont un outil puissant et gnral, mais ne sont pas la panace : un problme ne sest jamais rsolu par la seule vertu des objets.

Bibliographie
Rfrences gnrales
Actes Confrences ECOOP, European Conference on ObjectOriented Programming. Lecture Notes in Computer Science, Springer Verlag. vol. 276 (1987), vol. 322 (1988), vol. 512 (1991). Actes Confrences OOPSLA, Object-Oriented Programming, Systems, Languages, and Applications. Special Issue SIGPLAN Notices, ACM. vol. 21, n11 (1986), vol. 22, n12 (1987), vol. 23, n11 (1988), vol. 24, n10 (1989), vol. 25, n10 (1991). G. Agha. Actors : a Model of Concurrent Computation in Distributed Systems. MIT Press, Cambridge (Mass.), 1986. B.J. Cox. Object-Oriented Programming: an Evolutionary Approach. Addison-Wesley, Reading (Mass.), 1986. J. Ferber. Conception et Programmation par Objets. Collection Techniques de Pointe, Herms, Paris, 1990. G. Masini, A. Napoli, D. Colnet, D. Lonard, et K. Tombre. Les Langages Objets. InterEditions, Paris, 1989.

148 Les langages objets

B. Meyer. Conception et Programmation par Objets. InterEditions, Paris, 1989. B. Shriver et P. Wegner, editors. Research Directions in ObjectOriented Programming. MIT Press, Cambridge (Mass.), 1987. D. Tsichritzis, editor. Centre Universitaire dInformatique, Universit de Genve. Objects and Things, 1987. Active Object Environments. 1988. Object Management. 1990. O b j e c t Composition. 1991. A. Yonezawa et M. Tokoro, editors. Object-Oriented Concurrent Programming. MIT Press, Cambridge (Mass.), 1987.

Articles sur des aspects particuliers


Les citations entre crochets font partie des rfrences gnrales ci-dessus. G. Agha et C.E. Hewitt. Actors: a Conceptual Foundation for Concurrent Object-Oriented Programming. In [Shriver et Wegner, 1987], pp. 49-74. G. Agha et C.E. Hewitt. Concurrent Programming Using Actors. In [Yonezawa et Tokoro, 1987], pp. 37-53. P. Cointe. Implmentation et Interprtation des Langages Orients Objets. Application aux Langages Smalltalk, Objvlisp et Formes. Thse de Doctorat dtat, Universit de Paris VII, LITP 85.55, 1985. L. Cardelli et P. Wegner. On Understanding Types, Data Abstraction, and Polymorphism. ACM Computing Surveys, vol. 17, n4, pp. 471-522, 1985. W.R. Cook, W.L. Hill, et P.S. Canning. Inheritance is not Subtyping. Actes Principles of Programming Languages, ACM, pp. 125-135, 1990.

Bibliographie 149

S. Danforth et C. Tomlinson. Type Theories and ObjectOriented Programming. ACM Computing Surveys, vol. 20, n1, pp. 29-72, 1988. R. Ducourneau et M. Habib. La Multiplicit de lHritage dans les Langages Objets. Technique et Science Informatique, vol. 8, n1, pp. 41-62, 1989. K. Gorlen. An Object-Oriented Class Library for C++ Programs. Software Practice and Experience, vol. 17, n12, pp. 899-922, 1987. H. Lieberman. Using Prototypical Objects to Implement Shared Behavior in Object-Oriented Systems. In [Actes OOPSLA, 1986], pp. 214-223. B. Meyer. Genericity versus Inheritance. In [Actes OOPSLA, 1986], pp. 391-405. D. Ungar, C. Chambers, B-W. Chang, et U. Hlzle. Organizing Programs without Classes. International Journal of Lisp and Symbolic Computation, vol. 4, n3, 1991. A. Yonezawa, E. Shibayama, T. Takada, et Y. Honda. Modelling and Porgramming in an Object-Oriented Concurrent Programming. In [Yonezawa et Tokoro, 1987], pp. 55-89.

Langages de programmation par objets


Les citations entre crochets font partie des rfrences gnrales ci-dessus. Byte Special Issue: the Smalltalk-80 System. Byte, vol. 6, n8, 1981. L. Cardelli, J. Donahue, L. Glassman, M. Jordan, B. Kalsow, et G. Nelson. Modula-3 Report (revised). Research Report 52, DEC Systems Research Center, Palo Alto (Calif.), 1989. P. Cointe. Metaclasses are First Class: the ObjVLisp Model. In [Actes OOPSLA, 1987], pp. 156-167.

150 Les langages objets

O.J. Dahl et K. Nygaard. Simula, an Algol-based Simulation Language. Comm. of the ACM, vol. 9, n9, pp. 671-678, 1966. L.G. DeMichiel et R.P. Gabriel. The CommonLisp Object System: an Overview. In [Actes ECOOP, 1987], pp. 201-220. Eiffel: the Language. Interactive Software Engineering, Inc., Goleta (Calif.), 1989. M. Ellis et B. Stroustrup. The Annotated C++ Reference Manual. Addison-Wesley, Reading (Mass.), 1990. A. Goldberg et D. Robson. Smalltalk-80, the Language and its Implementation. Addison-Wesley, Reading (Mass.), 1983. S.E. Keene. Object-Oriented Programming in CommonLisp. A Programmers Guide to CLOS. Addison-Wesley, Reading (Mass.), 1989. H. Lieberman. Concurrent Object-Oriented Programming in Act1. In [Yonezawa et Tokoro, 1987]. S. Lippman. A C++ Primer. Addison-Wesley, Reading (Mass.), 1989. D. Moon. Object-Oriented Programming with Flavors. In [Actes OOPSLA, 1986], pp. 1-8. K. Schmucker. Object-Oriented Programming for the Macintosh. Hayden Book Company, Hasbrouck Heights, (New Jersey), 1986. D. Ungar et R.B. Smith. Self: the Power of Simplicity. In [Actes OOPSLA, 1987], pp. 227-242. A. Yonezawa, J-P. Briot, et E. Shibayama. Object-Oriented Concurrent Programming in ABCL/1. In [Actes OOPSLA, 1986], pp. 258-268.

Index
Ceyx 88 champ 13, 16, 65, 94 calcul 101 de classe 86, 127 classe 13, 14, 16, 33, 64, 82, 91, 97, 98, 118 abstraite 120, 123, 124, 132 active 119 agglomre 125, 134 amie 54 atomique 119 compose 119, 125, 134 conteneur 58, 119 de base 21 B dpendante 134 Bloc 64, 77, 94, 119 drive 21 Boolen 71, 74 gnrique 58 C primitive 38 C 2, 7, 9, 11, 15 ClasseObjet 83, 85 C++ 4, 8, 9, 31, 35, 47, 50, 51, ClassePile 83, 85 53, 55, 56, 58, 59, 84, clonage 94, 96, 97, 116 96, 120, 124, 126 CLOS 10, 88, 92 cache 73, 103 Clu 15 Carr 123 Collection 120, 122 Cartsien 101 combinaison 24 case 94 CommonLisp 2 A ABCL/1 104, 110 ACT1 104 acteur 11, 104 Ada 2, 4, 7, 15, 25 Afficheur 129 agglomration 125 Algol 2, 8 ami 54, 55, 126 arbre dhritage 21, 28, 69, 120 Arc 37

152 Les langages objets

communication 110 graphe dhritage 22, 27 comportement exceptionnel 99 H composition 24, 125 Hanoi 111, 136 constructeur 56, 59, 84, 96 HanoiG 137 continuation 105 hritage 14, 64, 69, 98, 121, Create 56 132 D dynamique 102 destructeur 57, 59 multiple 22, 27, 44, 90, 124 dlgation 96, 104, 116 priv 54, 124 Dmo 87 rpt 22, 124 dmon 90 simple 18, 22, 39 drivation parallle 134 HPile 71, 86, 134 dictionnaire des mthodes 28, I 66, 69, 73 Imprimante 129 domaine de visibilit 53, 128 initialisation 56, 84, 95 double distribution 129 instance 13, 14, 16, 64 E instanciation 16, 28, 33, 64, 66, Eiffel 5, 8, 31, 35, 47, 50, 55, 81, 84, 97 56, 58, 59, 84, 126, Intervalle 78 128, 135 invocation de mthode 17, 33, encapsulation 6, 15, 17, 52, 128 51, 65, 110, 129 enrichissement 21, 42, 69, 122, itrateur 119, 121, 122 123 J Entier 38, 64, 78, 79 jointure de continuations 109, envoi de message 13, 17, 34, 113 62, 104, 110, 116, 129 L langage F objets 13, 111, 140 facette 125 typage dynamique 4 factorielle 107 typage statique 4 Faux 74, 100 compil 6, 8, 92 Fentre 42, 44, 124, 129, 134 dacteurs 4, 11, 104 Fentre3D 134 de classes 94, 95, 96, 97, Flavors 88, 90 116 Forme 123 de prototypes 11, 93, 116 Fortran 2 faiblement typ 4, 135 G fortement typ 4, 27 gestion dynamique 59 hybride 11 gnricit 4, 7, 25, 58, 132 impratif 8 GPile 58 interprt 5, 61, 92

Index 153

non typ 5, 28, 61, 92, 103, 116, 128, 129 parallle 3, 104, 116 semi-compil 6, 10, 61 squentiel 3 typ 5, 8, 26, 60, 92, 96, 128, 129, 131, 134 Le_Lisp 2, 10, 88 liaison 28 dynamique 27, 48, 50, 81, 92, 103, 116 statique 27, 48 lien dhritage 14, 64, 93, 96 dinstanciation 14, 64, 65, 93, 96 Lisp 2, 5, 9, 10, 15, 30, 65, 87, 116 liste dexportation 55, 126

Modula3 4, 9, 31, 50, 53, 55, 59 modularit 6, 7, 116, 140 module 6, 15, 16 moi 36 O Object Pascal 9 Objective-C 11 objet 1, 13, 21, 33, 64, 68, 69, 83, 91, 94, 111 ObjVLisp 10, 88, 90 OGraphique 129, 134 OGraphique3D 134

P parent 96, 102 Pascal 2, 4, 7, 13, 15, 25, 59, 78 Pile 15, 16, 19, 33, 39, 67, 71, 79, 83, 84, 94, 102 PileAbstraite 133, 139 M Plasma 104 mandataire 104 Polaire 101 membre 53 Polygone 123 message 64, 104, 129 polymorphisme 24, 28 mots cls 62 ad hoc 24, 26, 34, 80 binaire 62, 72 dhritage 26, 48, 125, 129 express 110 dinclusion 25, 26 unaire 62 paramtrique 4, 25, 58 mta-circularit 10, 30, 84 priv 17, 54 mtaclasse 10, 28, 64, 81, 83, programmation 90, 127, 135 fonctionnelle 2, 18, 117 mthode 13, 16, 17, 34, 94, 125 imprative 2, 18, 117 amie 54 logique 2 daccs 125 modulaire 7 de calcul 126 par objets 3, 7, 18, 116, 117 de classe 29, 84, 127 Prolog 3 de construction 126 protg 54 de contrle 127 protocole 117, 122, 125, 132 prive 118 protoPile 96, 98 publique 117 protoPoint 101 redfinie 20 protoTour 98 virtuelle 8, 49, 120 prototypage 81, 92, 103, 128 ML 2, 15

154 Les langages objets

prototype 11, 94 pseudo-variable moi 36 self 72, 95 super 72 public 17, 54 Q Quadrilatre 123

Sommet 37 sous-classe 21, 64, 71 spcialisation 19, 21, 39, 122, 123 super 72 superclasse 21 surcharge 24, 131

T table virtuelle 51 R Tableau 67 ramasse-miettes 59, 60 Tour 19, 39, 42, 44, 57, 98, receveur 17, 34, 62, 105, 129 102, 111, 124, 136 Rectangle 42, 123 TourG 42, 44, 137 redfinition de mthode 20, 26, TourGM 44, 124 41, 69 Tours de Hanoi 18, 98, 111, rification 30 119, 136 rutilisation 6, 7, 60, 128, 132, typage 4, 8, 27, 28, 30, 49, 81, 134, 139, 140 92, 103, 135 rgles de visibilit 34, 52, 126, type 4, 16, 103 128 V S variable dinstance 65 Scheme 2 variable de classe 86 self 72 Voiture 125 Self 6, 10, 72, 94, 95 Vrai 74, 100 Simula 1, 4, 8, 31, 50 vue 55 Smalltalk 5, 6, 8, 9, 29, 61, 89, 91, 95, 100, 103, 118, 119, 135

Vous aimerez peut-être aussi