31
1 L’approche orientée objet Il est difficile de faire l’apprentissage efficace et réussi de Java et de C++ sans étudier l’approche orientée objet indépendamment de la syntaxe de ces deux langages. Pour certains principes de cette approche, Java et C++ peuvent d’ailleurs parfois présenter des spécificités. Pour éviter donc d’entrer trop rapidement dans les détails les plus abscons des deux langages, ce chapitre traite de l’orienté objet en général dans un esprit le plus didactique possible. Derrière ces deux langages se cachent les concepts fondamentaux de la programma- tion orientée objet. Dans ce chapitre, nous décrivons l’approche orientée objet sans jamais utiliser une ligne de code. Un dessin valant mieux qu’un long discours, nous proposons quelques figures fondées sur la notation graphique UML ( Unified Modeling Language ). La connaissance de cette notation n’est en aucun cas néces- saire pour aborder ce chapitre. Cependant, le lecteur peut se référer à [Barbier, 2005] pour d’éventuels approfondissements. Le début de ce chapitre décrit le passage de la dualité type/variable des langages de programmation « classiques » au modèle classe/instance de la programmation objet. Viennent après les fondamentaux de l’orienté objet : l’encapsulation, la différence entre propriétés d’instance et propriétés de classe, l’héritage et le polymorphisme, la généricité et la programmation défensive. Tous ces mécanismes caractérisent l’orienté objet mais ne sont pas exclusifs d’autres mécanismes de programmation plus spécialisés expliqués en détail et en profondeur pour Java et pour C++ dans les autres chapitres.

L’approche orientée objet

  • Upload
    others

  • View
    25

  • Download
    0

Embed Size (px)

Citation preview

Page 1: L’approche orientée objet

1

L’approche orientée objet

Il est difficile de faire l’apprentissage efficace et réussi de Java et de C++ sans étudierl’approche orientée objet indépendamment de la syntaxe de ces deux langages. Pourcertains principes de cette approche, Java et C++ peuvent d’ailleurs parfois présenterdes spécificités. Pour éviter donc d’entrer trop rapidement dans les détails les plusabscons des deux langages, ce chapitre traite de l’orienté objet en général dans unesprit le plus didactique possible.

Derrière ces deux langages se cachent les concepts fondamentaux de la programma-tion orientée objet. Dans ce chapitre, nous décrivons l’approche orientée objet sansjamais utiliser une ligne de code. Un dessin valant mieux qu’un long discours, nousproposons quelques figures fondées sur la notation graphique UML (

UnifiedModeling Language

). La connaissance de cette notation n’est en aucun cas néces-saire pour aborder ce chapitre. Cependant, le lecteur peut se référer à [Barbier, 2005]pour d’éventuels approfondissements.

Le début de ce chapitre décrit le passage de la dualité type/variable des langages deprogrammation « classiques » au modèle classe/instance de la programmation objet.Viennent après les fondamentaux de l’orienté objet : l’encapsulation, la différenceentre propriétés d’instance et propriétés de classe, l’héritage et le polymorphisme, lagénéricité et la programmation défensive. Tous ces mécanismes caractérisentl’orienté objet mais ne sont pas exclusifs d’autres mécanismes de programmationplus spécialisés expliqués en détail et en profondeur pour Java et pour C++ dans lesautres chapitres.

JavaC+ Livre Page 5 Lundi, 20. juillet 2009 8:50 08

Page 2: L’approche orientée objet

6

Conception orientée objet en Java et C++

1.1 Dualité classe/instance

Le couple classe/instance de la programmation objet étend le principe du coupletype/variable de la programmation structurée

1

(Fortran, COBOL, C, Pascal et biend’autres langages de programmation) qui a précédé l’orienté objet. Tout comme un

type

établit les propriétés d’une

variable

, une

classe

(une structure de données) estun moule, un gabarit, pour l’

instance

. L’instance est une incarnation, un exemplairereproductible à l’envi de sa classe.

Imaginons la création d’un logiciel d’éveil pour enfants, dans lequel évoluent deséléphants. Ce jeu fait apparaître à l’écran des éléphants et interroge les utilisateurs surdes propriétés de ces mammifères. Il est immédiatement nécessaire de décrire le type(la classe)

Éléphant

. En ce sens, à la figure 1.1, l’éléphant est considéré et étudié entant qu’espèce du règne animal.

À la figure 1.2, des instances réelles d’éléphants naissent et meurent au gré de la viemais respectent invariablement leur appartenance à une branche particulière de laclassification animale : celle imagée à la figure 1.1.

Une classe

Elephant

écrite en C++ est ébauchée à la section 2.3.6.

1. La programmation objet étant aussi « structurée », opposer dans l’absolu programmation objet etprogrammation structurée n’a pas de sens. En fait, nous utilisons le vocabulaire consacré qui veutque l’expression « programmation structurée » désigne souvent ce qui a précédé l’orienté objet.

Figure 1.1 •

L’espèce (la classe)

Éléphant

vue sous l’angle zoologique.

Figure 1.2 •

Des instances (Jumbo à gauche et Babar à droite) de la classe

Éléphant

.

JavaC+ Livre Page 6 Lundi, 20. juillet 2009 8:50 08

Page 3: L’approche orientée objet

L’approche orientée objet

7

1.1.1 Abstraction

L’abstraction est la faculté de représenter quelque chose incomplètement. La repré-sentation est délibérément réductrice de la chose pour s’affranchir de la complexitéinhérente à cette chose. Construire une classe en programmation objet est par défi-nition produire une abstraction.

Une entité logicielle est par essence un artefact, un élément artificiel qui imite incom-plètement un concept, une chose physique ou logique, une idée… tous issus d’une« réalité observée ». L’idée-clé des langages à objets a toujours été de fournir unsupport de description des « choses perçues » en espérant un fossé plus étroit avec lemonde réel.

Simplement, décrire la classe

Éléphant

en Java ou C++, c’est restreindre l’espèce deséléphants à quelques propriétés d’intérêt tout en en occultant bien d’autres qu’on nepourra jamais exhaustivement prendre en compte dans un ordinateur. Ainsi, si jeconstruis un logiciel d’éveil, ma représentation informatique des éléphants serésume, par souci de concision et d’efficacité, à ce que le jeu montre aux enfants : leurcouleur, leur corpulence… Ma classe ne décrira pas par exemple leur régime alimen-taire si le jeu n’exploite pas cette propriété.

1.1.2 Pourquoi les classes ?

À l’origine de la programmation, les types des variables n’ont pas toujours existé(dans les versions originelles du langage de programmation BASIC par exemple). Defaçon évidente et rationnelle, il a été nécessaire de contractualiser les usages des diffé-rentes variables en fonction des capacités des ordinateurs (taille maximale d’occupa-tion mémoire des nombres entiers, des nombres décimaux…) et des manipulations-types et récurrentes que faisaient les programmeurs de leurs données. Un entier peutainsi s’additionner avec un autre entier ; un éléphant peut barrir ou encore avoir unecertaine corpulence (mesurer une certaine hauteur…).

Les classes sont ainsi constituées de propriétés elles-mêmes typées. Les classesforment de nouveaux types. Il en résulte que chaque instance de la classe Éléphant aune certaine hauteur (valeur de la propriété), le concept de hauteur n’étant pas étrangeraux éléphants puisqu’il contribue à définir leur corpulence.

L’apport de la programmation objet est de dépasser les simples propriétés statiquescomme la hauteur pour s’intéresser aux propriétés dynamiques. Barrir est donc uneopération (un traitement) supportée par la classe des éléphants. Au-delà, l’opérationbarrir peut être paramétrée ; la puissance du barrissement peut ainsi dépendre de lacorpulence de l’éléphant en train de barrir, par exemple :

•Si corpulence « importante » alors son émis « fort » sinon si corpulence•« moyenne »…

L’informatique est fondée sur le principe universel de séparation entre traitements etdonnées. Les paradigmes de programmation exploitent ce principe de façon diffé-rente. Tandis que données et traitements sont pensés plutôt séparément en« approche traditionnelle », avec le paradigme objet, le confinement des traitements

JavaC+ Livre Page 7 Lundi, 20. juillet 2009 8:50 08

Page 4: L’approche orientée objet

8 ◆ Conception orientée objet en Java et C++

dans la structure de données (la classe) est essentiel. La figure 1.3 illustre ce principeoù le type Éléphant se caractérise à la fois par des propriétés statiques, les données(partie haute de la boîte), et dynamiques, les traitements (partie basse de la boîte).Les traitements restent séparés (graphiquement) des données. Cependant, ils nepeuvent pas concerner d’autres structures de données que la classe elle-même commel’incite la programmation structurée.

À l’exécution, les objets informatiques, ceux alloués dans la mémoire de l’ordinateur,sont établis en fonction du format à droite de la figure 1.3. La réalité présentée àla figure 1.2 (Jumbo et Babar) peut donc être « virtualisée » comme montré à lafigure 1.4.

À la figure 1.4, on voit que Babar barrit moins fort que Jumbo compte tenu de samoindre corpulence.

1.2 Encapsulation

L’encapsulation est un moyen technique de l’orienté objet pour instrumenterl’abstraction. Une partie de la classe est délibérément masquée (ou « encapsulée »), cequi rend plus aisé l’accès à la classe de l’extérieur. L’intérieur, invisible du côté utilisateur,rend de fait la classe moins complexe à utiliser.

Figure 1.3 • Conceptualisation de l’éléphant sous forme d’une classe informatique.

Figure 1.4 • Les deux éléphants réels à la figure 1.2 « virtualisés » dans un logiciel.

Éléphanthauteur

...

barrir...

Jumbo : Éléphanthauteur = 3,70

...

barrir...

Babar : Éléphanthauteur = 3,50

...

barrir...

JavaC+ Livre Page 8 Lundi, 20. juillet 2009 8:50 08

Page 5: L’approche orientée objet

L’approche orientée objet ◆ 9

Cette idée s’applique au logiciel de jeu dans lequel des éléphants produisent des sons,des barrissements en l’occurrence. Pour l’illustrer, prenons le cas de l’opérationbarrir, qui est fortement couplée à l’utilisation d’une carte son, de fichiers audiocodés dans des formats numériques nombreux et variés. Cette préoccupation esttechnique. Elle dérive d’une préoccupation plus amont : le besoin (dit « métier ») dujeu lui-même. Le jeu traite en effet d’animaux, d’éveil, de pédagogie, d’interactivité…pour divertir et éduquer.

1.2.1 Métier versus technologie

Le programmeur doit gérer deux flux de complexité. Celui des exigences d’abord,complexes par essence, qui sont relatives aux fonctionnalités « métier » que doitoffrir le jeu. Il y a ensuite la complexité technologique : connaître les formatsnumériques audio,savoir programmer des sons dans un logiciel… Ce phénomène dedouble flux est traité par le principe de séparation des préoccupations (separation ofconcerns en anglais).

Selon ce principe, on ne s’intéresse dans un premier temps qu’aux propriétés métier1.Ces propriétés sont logiquement visibles de l’extérieur (on dit aussi « publiques »,voir les littéraux public de Java et public: de C++ utilisés dans les autres chapitresde cet ouvrage). Les propriétés technologiques doivent, elles, être immergées2 demanière que tout programmeur (ré)utilisant la classe Éléphant puisse faire barrir sesobjets logiciels éléphants sans avoir de connaissance spéciale sur les technologies àmême de produire des sons dans un ordinateur. Il en résulte que les classes au départuniquement dotées de propriétés métier (voir figure 1.3) s’étoffent avec des propriétésplus techniques (voir figure 1.5)3.

À la figure 1.5, on préfixe le signe + aux propriétés métier pour dire qu’elles sont visi-bles et donc manipulables de l’extérieur alors que la nouvelle propriété source son detype Fichier audio est indiquée privée (signe -). On dit que cette dernière est« encapsulée ».

1. Cette activité est souvent appelée « analyse orientée objet ».2. On dit aussi plus volontiers « privées » ou « protégées », principes supportés par les littéraux private

et protected de Java, et private: et protected: de C++.

Figure 1.5 • Prise en charge progressive de considérations technologiques dans les classes.

3. Cette phase suivant la phase d’analyse orientée objet est souvent nommée « conception orientéeobjet ». A contrario, conception orientée objet recouvre chez certains (par exemple [Barnes et Kölling,2005]) à la fois la découverte et la formalisation des propriétés métier et techniques.

Éléphant+ hauteur

- source son : Fichier audio...

+ barrir...

JavaC+ Livre Page 9 Lundi, 20. juillet 2009 8:50 08

Page 6: L’approche orientée objet

10 ◆ Conception orientée objet en Java et C++

La section 8.1 approfondit les notions d’encapsulation et de visibilité en Java et C++.

La philosophie objet promeut une approche continue, sans rupture brutale en toutcas, de description des propriétés (idée de seamless development en anglais). Ons’affranchit ainsi de la technologie au début de la fabrication des classes. Ensuite, lescontraintes techniques de toute nature sont satisfaites au gré des possibilités, éven-tuellement différentes, offertes par Java et C++. Comment programmer des sons enJava ou en C++ ? Puis comment programmer des sons en Java sur un téléphoneportable ou sur un ordinateur de bureau ? Etc. Cette migration en douceur vers lasolution logicielle proprement dite dans un environnement d’exécution presque« hostile » est une des justifications de l’apport de l’orienté objet comparé à laprogrammation structurée.

1.2.2 Réutilisation

La réutilisation est le pouvoir de ne pas recréer du code et donc des classes lorsque lesbesoins changent. C’est un enjeu économique important car produire des classescoûte cher. Pratiquement, l’idée de la réutilisation est de modifier le moins possibleles classes existantes ; des réglages sont toujours néanmoins nécessaires. Une bonneencapsulation favorise la réutilisation.

Il est important de toujours avoir à l’esprit que le concepteur d’une classe est rare-ment celui qui va l’utiliser (appelons ce dernier le « réutilisateur »). Le concepteurdoit favoriser au maximum la séparation entre usages et contraintes technologiques :système d’exploitation, formats multimédias, type et nature des périphériquesjusqu’aux écrans limités en capacité sur les téléphones portables par exemple. Si lelogiciel de jeu mettant en scène des éléphants doit à la fois fonctionner sur un télé-phone portable et un ordinateur familial, il s’agirait de pouvoir entendre le barris-sement dans les deux environnements d’exécution. La classe serait alors fortementréutilisable car indépendante de sa plate-forme technologique de déploiement.

Cet objectif de la programmation objet n’est pas toujours atteint. La qualité d’uneclasse, quant à sa réutilisation particulièrement, dépend d’une bonne encapsulation.Au-delà, l’encapsulation accroît la maintenabilité des programmes au sens où l’utili-sation d’un nouveau format audio plus performant, par exemple, ne doit pas fairevarier les usages. En clair, le concepteur de la classe change son code pour s’appuyersur ce nouveau format mais le réutilisateur, idéalement, ne doit pas avoir à changer lesien. C’est justement l’encapsulation qui permet cela.

1.2.3 Forte cohésion, faible couplage

Une réflexion profonde et une énergie importante consacrées à la fabrication des classesont donc un impact significatif sur l’accroissement de leur qualité : réutilisabilité, main-tenabilité mais aussi fiabilité pour résister aux bogues. L’idée de forte cohésion sous-tendqu’une classe ne doit incorporer ni plus ni moins que ce qui lui est « logiquement » attri-buable. Imaginons que nous devions non seulement connaître la hauteur des éléphantsmais aussi la température de leur corps, cela dans des unités de mesure différentes,comme les pieds pour la hauteur et les degrés Fahrenheit pour la température.

JavaC+ Livre Page 10 Lundi, 20. juillet 2009 8:50 08

Page 7: L’approche orientée objet

L’approche orientée objet ◆ 11

Dans un tel contexte, notre formalisation à la figure 1.3 devient inappropriée. Lafigure 1.6 reconsidère ce qui est proposé à la figure 1.3 via l’introduction de deuxnouveaux types qui sont Corpulence (non explicité à la figure) et Température.

La classe Température prend en charge des services retournant une valeur detempérature dans l’unité désirée (par exemple asCelsius), s’appuyant pour cela surdes propriétés internes cachées à tout réutilisateur potentiel. La classe Températureen version Java et C++ est détaillée à la section 2.3.4. Il aurait été illogique d’asso-cier à la classe Éléphant des calculs de conversion de hauteurs (de mètres en pieds etvice-versa) ainsi que des calculs de conversion de températures. La création des typesCorpulence et Température vise donc à isoler et à supporter de tels calculs.

Le faible couplage est le pendant de la forte cohésion. L’idée est d’établir des dépen-dances explicites entre classes, et cela a minima. À la figure 1.6, la classe Éléphants’appuie sur les classes Corpulence et Température via respectivement les propriétéscorpulence et température du corps. On peut néanmoins créer et manipuler des instan-ces de Température sans dépendre du concept d’Éléphant. Cette bonne isolationglobale des concepts a beaucoup d’avantages. Si le jeu d’éveil pour enfants doit êtremultilingue, avec par conséquent des unités de mesure propres à chaque pays, l’orga-nisation en classes à la figure 1.6 est judicieuse. Concernant la fiabilité, les panneséventuelles du logiciel sont mieux localisées grâce à une telle organisation. Certes, ilfaudra toujours corriger des bogues résiduels mais il est bien connu que leur localisa-tion est souvent une tâche plus compliquée que leur correction proprement dite. Laréutilisabilité croît aussi par le caractère universel de la classe Température, qui peuts’intégrer aisément dans une application autre que le jeu pour enfants, une applicationde contrôle d’un chauffage par exemple.

En résumé, l’approche objet dépasse le simple fait de « programmer ». C’est la modé-lisation objet qui est prédominante : comment bien décrire les classes et leursdépendances ? Jusqu’ici, sans écrire une seule ligne de Java ou de C++, on observeque l’orienté objet met en avant une problématique de développement logiciel origi-nale. L’approche objet est en tout cas différente de l’approche fonctionnelle privilé-giant la modélisation des fonctions puis leur décomposition en sous-fonctions. Lesstructures de données se déduisent alors à partir des fonctions élémentaires. L’orientéobjet cherche à éviter cela car des incohérences et effets de bord apparaissent sur lelong terme, c’est-à-dire au fur et à mesure de l’évolution du code pour intégrer denouveaux besoins.

Figure 1.6 • Distribution des propriétés dans des classes différentes pour une plus forte cohésion.

Éléphant+ corpulence : Corpulence

+ température du corps : Température...

+ barrir...

Température

+ asCelsius : Real+ asFahrenheit : Real

+ asKelvin : Real...

...

JavaC+ Livre Page 11 Lundi, 20. juillet 2009 8:50 08

Page 8: L’approche orientée objet

12 ◆ Conception orientée objet en Java et C++

1.3 Propriétés de classe ou propriétés d’instance

Une propriété d’une classe peut ne pas varier d’une instance à l’autre : elle est dite« de classe », sinon elle est « d’instance ». Pour l’illustrer, considérons dans le jeule fait de sensibiliser les enfants à la distinction entre éléphants d’Asie etéléphants d’Afrique (voir figure 1.7). Nous aimerions en particulier avoir lapossibilité d’afficher les mots savants de chaque sous-espèce à savoir « Elephasmaximus » pour les éléphants d’Asie et « Loxodonta africana » pour les pachydermesafricains.

1.3.1 Attributs de classe

La figure 1.8 (à gauche) étend pour cela notre classe initiale avec les propriétés idoines.L’attribut sous-espèce permet de mémoriser si une instance d’éléphant est asiatique ouafricaine. Pour Jumbo par exemple (à droite de la figure 1.8), l’attribut sous-espèce estégal à "Afrique".

L’image de Jumbo dans la mémoire de l’ordinateur montre néanmoins deux problè-mes. Le premier est que, contrairement à la propriété hauteur (Jumbo peut grandirou rapetisser pendant sa vie), l’attribut sous-espèce est constant. Ce problème est aisé-ment résolu avec les mots-clés const de C++ et final de Java (voir leur usage respectifau tout au long de l’ouvrage). Les attributs Nom savant Asie et Nom savant Afrique

Figure 1.7 • Éléphant d’Asie versus éléphant d’Afrique.

Figure 1.8 • Prise en compte du critère de sous-espèce des éléphants.

Éléphant

+ hauteur+ sous-espèce

+ Nom savant Asie = "Elephas maximus"+ Nom savant Afrique = "Loxodonta africana"

Jumbo :Éléphant

hauteur = 3,70sous-espèce = "Afrique"

Nom savant Asie = "Elephas maximus"Nom savant Afrique = "Loxodonta africana"

+ barrir...

barrir = ...

JavaC+ Livre Page 12 Lundi, 20. juillet 2009 8:50 08

Page 9: L’approche orientée objet

L’approche orientée objet ◆ 13

sont eux aussi constants, mais leur nature invariable est surtout indépendante dechaque éléphant. En clair, à la figure 1.8, Jumbo possède en propre ces deux valeursdans la mémoire qui lui est réservée. Cette solution n’est pas optimale car on dupli-que en effet inutilement ces deux attributs pour tous les éléphants. Puisque les valeurssont communes à toutes les instances possibles du type Éléphant, autant attribuer cesdeux propriétés à la classe elle-même : c’est ce que l’on appelle une propriété declasse. Appliquant ce précepte, la figure 1.9 montre comment l’on doit idéalementorganiser la mémoire pour éviter toute redondance.

En fait, le cliché de la mémoire à la figure 1.9 est représentatif de ce qui se fait dansJava et C++. Une zone mémoire est réservée pour représenter la classe Éléphant, elle-même indépendamment de toute instance de cette classe qui serait créée. Jumbo etBabar possèdent toujours les propriétés constantes Nom savant Asie et Nom savantAfrique mais les partagent (via un simple pointeur par exemple) plutôt que d’enposséder une copie propre. Reste à savoir quel est le niveau d’accès laissé au program-meur à la classe Éléphant vue elle-même comme un « objet ». Alors que C++ estlimité sur le sujet, Java propose une classe particulière appelée Class1 (voir figure 1.9).Jumbo est ainsi une instance de la classe Éléphant, elle-même instance de la classeClass. Class est appelée méta-classe de Jumbo. Ce principe est celui de l’introspec-tion (aussi appelée « réflexion »). Il est expliqué et illustré à la section 8.2 de cetouvrage.

La figure 1.9 montre comment on agence les instances dans la mémoire pour accéderen particulier à leurs attributs de classe. Avec UML, il est néanmoins nécessaire dedisposer d’une notation graphique dédiée pour différencier attributs de classe etd’instance. Avec UML, on souligne les propriétés de classe (voir figure 1.10) pour lesdistinguer des propriétés d’instance.

Figure 1.9 • Organisation mémoire pour représenter les propriétés de classe.

1. Depuis Java 5, cette classe est générique. Par souci de simplicité, nous n’entrons pas dans ces détailsici.

Jumbo : Éléphanthauteur = 3,70

sous-espèce = "Afrique"…

barrir =...

barrir =...

Babar : Éléphanthauteur = 3,50

sous-espèce = "Afrique"…

Éléphant : ClassNom savant Asie = "Elephas maximus"

Nom savant Afrique = "Loxodonta africana"…

« pointe sur » « pointe sur »

JavaC+ Livre Page 13 Lundi, 20. juillet 2009 8:50 08

Page 10: L’approche orientée objet

14 ◆ Conception orientée objet en Java et C++

1.3.2 Fonctions de classeAu même titre qu’il y a des attributs de classe, il y a des fonctions de classe. Les attri-buts Nom savant Asie et Nom savant Afrique ont pour valeur des expressions latines.On peut imaginer de fournir des traductions dans une langue vivante : français,anglais, espagnol… Il faut alors deux fonctions de classe idoines : Traduire nomsavant Asie et Traduire nom savant Afrique (voir figure 1.11). L’algorithme de ces deuxfonctions consiste à tester le paramètre langue et, en fonction de sa valeur, à retournerla chaîne de caractères adéquate.

De façon logique, les fonctions de classe ne manipulent que les attributs de classealors que les fonctions d’instance peuvent manipuler (à la fois) les attributsd’instance et de classe. Ces derniers sont partagés selon le modèle à la figure 1.9.

En résumé, la dichotomie entre propriétés de classe et propriétés d’instance est unoutil puissant d’organisation des classes et des programmes. Sans être équivalente, lanotion de propriété de classe est comparable à celle de variable globale, qui disparaîten orienté objet. Les propriétés de classe permettent de simuler les variables globalestout en rendant l’organisation du code plus propre.

Figure 1.10 • Caractérisation d’attributs de classe.

Figure 1.11 • Caractérisation de fonctions de classe.

Éléphant+ hauteur

+ sous-espèce+ Nom savant Asie = "Elephas maximus"

+ Nom savant Afrique = "Loxodonta africana"…

+ barrir ...

Éléphant+ hauteur

+ sous-espèce+ Nom savant Asie = "Elephas maximus"

+ Nom savant Afrique = "Loxodonta africana"…

+ barrir + Traduire nom savant Asie (langue)

+ Traduire nom savant Afrique (langue)…

JavaC+ Livre Page 14 Lundi, 20. juillet 2009 8:50 08

Page 11: L’approche orientée objet

L’approche orientée objet ◆ 15

1.4 Héritage, polymorphisme et typage

L’héritage et le polymorphisme sont des mécanismes d’appariement des classes envue de rationaliser l’organisation des bibliothèques de code et des programmes.Ils sont tous deux fortement liés au typage et aux relations formelles possibles entretypes, au sens où une classe forme par définition un type.

L’approche objet incite fortement à concevoir les entités logicielles comme desabstractions fidèles d’objets « extraits » du monde réel, qu’ils soient physiques(éléphants…) ou logiques (factures…). Dans la réalité, « les choses » entretiennentdes relations spatiales, temporelles… mais plus profondément d’appartenance à unemême catégorie. Deux catégories peuvent être proches, comme les éléphants d’Asieet les éléphants d’Afrique. Ces deux catégories appartiennent en effet au règneanimal, et plus spécifiquement à la catégorie des mammifères, incluant elle-mêmecelle des éléphants. C’est la notion d’héritage au sens ici d’héritage d’un patri-moine génétique.

Une telle analyse peut être infinie jusqu’à distinguer l’ADN de deux individus. Pourl’arrêter dans le cas des éléphants, il suffit de se limiter aux besoins du logiciel quel’on construit. Soit la distinction éléphant d’Afrique et éléphant d’Asie est purementinformative, et là, on peut se cantonner au schéma à la figure 1.10. Soit la distinctiona un rôle plus important, dans le jeu qui nous intéresse particulièrement, et là, il fautprocéder différemment. Faisons l’hypothèse que le barrissement des éléphants estsoumis au critère de sous-espèce : les éléphants d’Afrique barrissent avec un songrave alors que leurs cousins asiatiques barrissent avec un son aigu. De façon oppor-tuniste, notre jeu d’éveil peut exploiter cette propriété pour attirer plus spécialementl’attention des enfants dès qu’un éléphant est à l’écran et barrit. En termes didac-tiques, l’enfant peut faire des associations petites oreilles-son plus aigu-Asie versusgrandes oreilles-son plus grave-Afrique.

L’algorithme de production de son associé à l’opération barrir (voir section 1.1.2) secomplexifie alors sous la forme suivante :

•Si corpulence « importante » alors

• Si sous-espèce égale à "Afrique" alors son émis fort et « plus grave »

• sinon si sous-espèce égale à "Asie" alors…

•Sinon si corpulence « moyenne » alors…

Ainsi l’opération barrir qui semble à l’origine identique a finalement un effet différentselon la sous-espèce : c’est la notion de polymorphisme.

La complexification qui précède n’a pas lieu de s’arrêter dans le cadre tout à fait natu-rel de l’évolution des besoins du jeu. En termes de maintenance, le programmeur dela classe Éléphant va donc retoucher son code à la demande, le tester à nouveau et lemettre en exploitation, tout en espérant qu’il n’a pas introduit de nouveaux bogues1.

1. Le lecteur doit extrapoler la réalité du terrain à partir de l’exemple. À grande échelle en effet, il fautfaire un nombre énorme de retouches, affectant la qualité du code.

JavaC+ Livre Page 15 Lundi, 20. juillet 2009 8:50 08

Page 12: L’approche orientée objet

16 ◆ Conception orientée objet en Java et C++

L’héritage et le polymorphisme sont des mécanismes caractéristiques de la program-mation objet. La totalité du chapitre 5 leur est consacrée tant ces deux supports deprogrammation jouent un rôle essentiel dans la qualité des programmes.

1.4.1 Héritage de structure

L’héritage permet d’abord d’apparier des structures de données : c’est l’héritage destructure. Une classe héritant d’une autre dispose pour ses instances des propriétés desa classe ascendante directe (voir figure 1.12), et plus si cette dernière a elle-mêmeune ascendance.

À la figure 1.12, l’attribut sous-espèce a purement et simplement disparu. En effet, letype direct d’un objet est implicitement connu et implémenté dans des langagescomme Java et C++ (voir en particulier les sections 8.2.1 et 8.2.2). Ici, le champd’instance hauteur est hérité (il provient de la classe Éléphant), d’où son absence dansles classes Éléphant d’Asie et Éléphant d’Afrique. Concernant les attributs de classeNom savant Asie et Nom savant Afrique, ils profitent aussi de cette toute nouvelleorganisation pour migrer aux endroits idoines. Bref, on se rend compte que structu-rellement l’héritage évite de réécrire une structure de données en entier à partir dezéro. De plus, le placement des propriétés tient compte de la nature générale d’untype (ici Éléphant) au regard de celle plus spécialisée de ses sous-types. Ce processusde conception est sans limites, d’où l’idée de hiérarchies de classes complètes etprofondes axées sur l’héritage.

Imaginons maintenant la distinction entre éléphants d’Afrique vivant dans la savaneet éléphants d’Afrique vivant dans la forêt. Un attribut savane-versus-forêt placé dansla classe Éléphant elle-même serait inadéquat car cet attribut n’a aucune utilité pourles éléphants d’Asie. Grâce à la hiérarchie d’héritage, cet attribut peut se placer dans laclasse Éléphant d’Afrique seulement. Une autre solution consiste à introduire deuxsous-classes de la classe Éléphant d’Afrique. Pour arbitrer entre ces deux choix possi-bles, il faut comprendre l’héritage de comportement, qui se conjugue à l’héritage destructure.

Figure 1.12 • Héritage symbolisé par un triangle blanc.

Éléphant d'Asie+ Nom savant Asie = "Elephas maximus"

+ barrir …

Éléphant d'Afrique+ Nom savant Asie = "Loxodonta africana"

+ barrir …

Éléphant+ hauteur

+ barrir …

JavaC+ Livre Page 16 Lundi, 20. juillet 2009 8:50 08

Page 13: L’approche orientée objet

L’approche orientée objet ◆ 17

1.4.2 Héritage de comportement, polymorphisme

L’héritage de comportement concerne plus volontiers les opérations dans les classesalors que l’héritage de structure concerne les attributs. Le polymorphisme est engen-dré par l’héritage de comportement alors qu’il n’a pas de corrélation avec l’héritage destructure.

La fonction barrir est une propriété dynamique. Elle est aussi héritée par les classesdescendantes mais elle apparaît de façon redondante (en apparence) dans les classesÉléphant d’Asie et Éléphant d’Afrique. Logiquement, comme le champ hauteur, ellen’aurait pas dû être réécrite car héritée. Intéressons-nous à la caractérisation de lafonction barrir dans la classe Éléphant pour commencer :

•Si corpulence « importante » alors son émis « fort » sinon si corpulence•« moyenne » alors…

Dans cette caractérisation, il n’est plus possible de tenir compte du fait que l’éléphantest d’Asie ou d’Afrique car l’attribut sous-espèce a disparu à la figure 1.12. Le sonproduit est donc fondé (entre autres) sur la valeur du champ hauteur d’un éléphant,participant par définition à sa corpulence.

Si le logiciel crée des instances de la classe Éléphant, il n’est donc pas en mesure derendre le son plus aigu ou plus grave pour tenir compte de la spécificité du barris-sement des éléphants d’Asie ou d’Afrique, respectivement. En clair, Babar et Jumbo,tels qu’ils apparaissent à la figure 1.4, barrissent de façon non caractéristique : le sonne peut être accentué ni vers les aigus, ni vers les graves.

Redéfinition

On est donc forcé à établir une version plus élaborée de la fonction barrir (ou redéfi-nition) dans la classe Éléphant d’Afrique (l’approche est duale pour Éléphant d’Asie) :

•Si corpulence « importante » alors son émis « fort » et « plus grave »•sinon si corpulence « moyenne » alors…

Pour tirer parti de cette fonction barrir plus étayée, il suffit maintenant d’instancierBabar et Jumbo comme des éléphants d’Afrique (voir figure 1.13).

La fonction barrir dans la classe Éléphant d’Afrique est une redéfinition de la mêmefonction dans la classe Éléphant. On la réécrit dans les classes Éléphant d’Asie etÉléphant d’Afrique pour exprimer la redéfinition.

Figure 1.13 • Babar et Jumbo directement instanciés comme des éléphants africains.

Jumbo : Éléphant d'Afriquehauteur = 3,70

...

barrir = et « plus grave »...

barrir = et « plus grave »...

Babar : Éléphant d'Afriquehauteur = 3,50

...

JavaC+ Livre Page 17 Lundi, 20. juillet 2009 8:50 08

Page 14: L’approche orientée objet

18 ◆ Conception orientée objet en Java et C++

Polymorphisme

La programmation objet fait foncièrement la distinction entre le type déclaré d’unobjet et la façon dont ce dernier est créé puis connu en mémoire. En l’occurrence,l’information se trouvant en mémoire pour connaître à tout moment le type « àl’exécution » est soit le type déclaré soit celui d’un de ses sous-types. Le polymor-phisme exploite cette information présente en mémoire.

Dans la philosophie objet, au regard de la figure 1.13, le type Éléphant d’Afrique est letype direct de Babar et Jumbo, mais Éléphant est aussi leur type. Il est un type indirecten l’occurrence. La figure 1.4 et la figure 1.13 montrent donc juste les deux manièresdont Babar et Jumbo peuvent être créés dans la mémoire de l’ordinateur. L’expressionqui suit est donc correcte :

•Babar, Jumbo : Éléphant // type déclaré mais Babar et Jumbo vont être•créés comme des éléphants africains

Ou alors on peut être plus précis :

•Babar, Jumbo : Éléphant d’Afrique // autre type déclaré possible

Voyons pourquoi la première forme, moins précise, est néanmoins intéressante carelle donne de la flexibilité. Soit le programme illustrant cette flexibilité :

•Éléonore : Éléphant•Éléonore est créée en tant qu’instance d’éléphant d’Afrique•Éléonore barrit•Éléonore meurt… sniff…•Éléonore est recréée sous forme d’Éléphant d’Asie, son nom est recyclé en fait•Éléonore barrit

À regarder de près, on ne peut pas différencier les deux instructions en gras duprogramme. C’est le polymorphisme qui signifie plusieurs formes d’exécution possi-bles associées à la même instruction. Le premier barrissement est plus grave car Éléo-nore est alors le pseudonyme d’un éléphant africain (créé en tant que tel dans lamémoire). Ensuite, le barrissement est plus aigu car le pseudonyme Éléonore a étéréaffecté à un éléphant asiatique. Comment peut-on réellement « jouer » le polymor-phisme en pratique ? En testant à l’exécution le type direct de l’objet sur lequel unefonction est mise en œuvre. Cela confirme que le type des objets est répertorié enmémoire. Au-delà, il faut déterminer dynamiquement (notions de late binding oudynamic binding pour « liaison retardée » ou « liaison dynamique » en français) où setrouve le bon code à lancer en fonction du type effectif d’Éléonore. Le type effectifd’Éléonore n’est pas celui de déclaration (Éléphant précédemment), mais celui repré-sentant effectivement l’objet dans la mémoire au moment de l’activation debarrir.(Éléphant d’Afrique puis Éléphant d’Asie).

La figure 1.14 montre comment Java et C++ s’arrangent pour faire fonctionner lepolymorphisme dans la mémoire du programme. Une table est utilisée pour gérertoutes les correspondances. L’exploitation de cette table crée un surcoût en tempsd’exécution du programme, d’où la possibilité en C++ d’occulter purement etsimplement le polymorphisme alors qu’il est toujours actif en Java. Cette dégradation(modique) des performances a de nombreuses contreparties. Une compensation

JavaC+ Livre Page 18 Lundi, 20. juillet 2009 8:50 08

Page 15: L’approche orientée objet

L’approche orientée objet ◆ 19

importante est la programmation « générique »1 que favorise le polymorphisme. Parexemple, on peut demander à un groupe d’éléphants dans une représentation d’uncirque de barrir, sans véritablement connaître ou savoir de quelle sous-espèce chacunprovient. Le polymorphisme crée donc une sorte d’adaptation automatique à larequête demandée :

•G : groupe d’instances de la classe Éléphant

•Pour chaque e du groupe G, e barrit

1.4.3 Classe abstraite

Une classe abstraite n’est pas instanciable, directement en tout cas. Une classe quel-conque étant une abstraction de la réalité, une classe abstraite ne signifie pas la mêmechose et a un sens technique. Java et C++ ont une syntaxe spéciale pour ce senstechnique. Une classe abstraite ne s’utilise et n’a d’intérêt qu’avec l’héritage et le poly-morphisme. C’est un corollaire de ces deux mécanismes.

À travers l’étude du polymorphisme, il apparaît opportun de déclarer Babar et Jumbodu type Éléphant, mais l’intérêt de continuer à les créer en mémoire, comme à lafigure 1.4, est discutable. En effet, le défaut, rappelons-le, est de ne pas pouvoir accen-tuer vers les aigus ou les graves leur barrissement car Babar et Jumbo sont des instancesdirectes de la classe Éléphant : aucune information n’existe sur le sous-type. Au-delà,l’architecture d’héritage la plus aboutie ici, celle à la figure 1.12, présente trois varia-tions de la fonction barrir (voir figure 1.14), qui incorporent toutes trois le mêmemorceau d’algorithme :

•Si corpulence « importante » alors son émis « fort » <et plus < … >>

•sinon si corpulence « moyenne » alors…

Les parties entre <> sont optionnelles et/ou dépendantes du critère de sous-espèce.En fait, le test de corpulence ne repose que sur des attributs de la classe Éléphantseule. Si ce test s’affine, le programmeur est obligé de modifier l’algorithme à chaqueendroit où il se trouve, à savoir pour l’instant dans Éléphant, Éléphant d’Asie etÉléphant d’Afrique.

1. L’adjectif « générique » est ici utilisé au sens large. Il a un autre sens plus technique en programma-tion objet. Voir le reste de ce chapitre ou le chapitre 3 sur la généricité Java et C++.

Figure 1.14 • Les rouages cachés du polymorphisme.

Éléphant d’Afrique barrir

Éléphant barrir

Éléphant d’Asie barrir

Si corpulence « importante » alors son émis « fort »sinon si corpulence « moyenne » alors…

Si corpulence « importante » alors son émis « fort » et« plus grave » sinon si corpulence « moyenne » alors…

Si corpulence « importante » alors son émis « fort » et« plus aigu » sinon si corpulence « moyenne » alors…

JavaC+ Livre Page 19 Lundi, 20. juillet 2009 8:50 08

Page 16: L’approche orientée objet

20 ◆ Conception orientée objet en Java et C++

Deux raisons valables nous mènent à voir la classe Éléphant comme une classeabstraite. Avec UML, son nom apparaît alors en italique (voir figure 1.15).

Une classe abstraite est par définition en Java et en C++ une classe à partir de laquelleil est impossible de créer (directement) des objets. Il se produit des erreurs de compi-lation si une tentative d’instanciation a lieu. Il résulte de cela que le processus de créa-tion d’instances d’éléphants, comme imagé à la figure 1.4, est maintenant inhibé. Onrépond juste à la préoccupation d’avant : empêcher de créer Babar et Jumbo commedes instances (directes) de la classe Éléphant vu que cela a maintenant de moins enmoins d’intérêt.

La figure 1.15 apporte d’autres modifications. La première est que la fonction barrir adisparu dans Éléphant d’Asie et Éléphant d’Afrique. C’est la réponse à notre secondepréoccupation : passer de trois versions de cette fonction qui partagent des blocs decode semblables à une seule version, cela pour éviter les problèmes de maintenance,comme le maintien de la cohérence entre les trois versions en particulier. Voyons laversion unique de barrir écrite dans la classe Éléphant :

•Si corpulence « importante » alors

• Si sous-espèce égale à "Afrique" alors son émis fort et « plus grave »

• sinon si sous-espèce égale à "Asie" alors…

•Sinon si corpulence « moyenne » alors…

Cette version est la même que celle présentée au début de la section 1.4. La différencenotable cependant est qu’elle repose maintenant sur une fonction sous-espèce1 plutôtque sur un attribut sous-espèce (solution proposée à la figure 1.10 et à la figure 1.11).De plus, la fonction sous-espèce apparaît à la figure 1.15 à la fois dans les classesÉléphant, Éléphant d’Asie et Éléphant d’Afrique. Dans la classe Éléphant, elle est écriteen italique, signifiant qu’elle est abstraite. Dans les classes Éléphant d’Asie et Éléphant

Figure 1.15 • La classe Éléphant devient abstraite.

1. Le lecteur averti peut s’étonner que la fonction sous-espèce ne soit pas vue comme une fonction declasse (voir section 1.3.2). En fait, une fonction de classe ne peut pas être abstraite, en objet en généralet en Java et C++ en particulier.

Éléphant d'Asie+ Nom savant Asie = "Elephas maximus"

+ sous-espèce = "Asie"…

Éléphant d'Afrique+ Nom savant Asie = "Loxodonta africana"

+ sous-espèce = "Afrique"…

Éléphant+ hauteur

+ barrir+ sous-espèce

JavaC+ Livre Page 20 Lundi, 20. juillet 2009 8:50 08

Page 17: L’approche orientée objet

L’approche orientée objet ◆ 21

d’Afrique, elle n’est pas écrite en italique, elle est donc « concrète » par opposition à« abstraite ».

La notion de fonction abstraite est le pendant de celle de classe abstraite. Une fonc-tion abstraite est une fonction « vide », c’est-à-dire sans algorithme, sans code, sanscomportement associé… Comment l’algorithme de la fonction barrir dans Éléphant(voir précédemment) peut-il alors fonctionner ? En fait, il ne fonctionne pas car lafonction sous-espèce (dans Éléphant) « ne fonctionne pas ». Elle est vide, comme nousl’avons dit.

Comment permettre à une instance directe de la classe Éléphant de barrir ? C’est unfaux problème ; la classe Éléphant étant devenue abstraite, le cas ne peut pas arriverpuisque cette classe n’est plus instanciable. Au contraire, comment garantir qu’uneinstance de la classe Éléphant d’Afrique puisse barrir ? Cette classe dispose en effet dela fonction barrir héritée d’Éléphant mais ne la redéfinit plus (voir figure 1.15).Ce point est avantageux car on évite maintenant d’avoir à gérer trois versions de lafonction barrir (voir figure 1.12 et figure 1.14). De plus, la fonction sous-espèceretourne un résultat ("Afrique" en l’occurrence) dans la classe Éléphant d’Afrique.L’approche est duale dans Éléphant d’Asie. Vu que la fonction sous-espèce n’est pasabstraite dans Éléphant d’Afrique et Éléphant d’Asie, elle retourne respectivement lachaîne de caractères "Afrique" et "Asie". La valeur retournée est donc totalementcohérente avec la façon dont l’algorithme de la fonction barrir dans Éléphant a étéécrit juste avant. La fonction sous-espèce dans Éléphant d’Afrique est héritéed’Éléphant et rendue concrète ; idem dans Éléphant d’Asie. On peut d’ailleurs direqu’elle est définie dans Éléphant d’Afrique et Éléphant d’Asie plutôt que redéfiniepuisqu’elle n’a jamais été définie dans Éléphant.

Le principe de classe abstraite combiné au polymorphisme est donc un facteurimportant de qualité des programmes. D’un point de vue technique, le modèled’organisation mémoire présenté à la figure 1.14 s’étoffe pour aller vers celui explicitéà la figure 1.16.

En résumé, l’usage concomitant du polymorphisme et des classes abstraites peutparaître compliqué. Les bénéfices escomptés sont en fait économiques : réutilisabilité,maintenabilité et fiabilité. Plus simplement, pourquoi ne pas écrire le logiciel de jeu

Figure 1.16 • Polymorphisme conjugué à classe abstraite.

si type égal à Éléphant d’Afrique

si type égal à Éléphant d’Asie

sous-espèce Éléphant d’Asie barrir Héritée sans redéfinition

Vide

Retourne "Afrique"

Retourne "Asie"

Héritée sans redéfinitionsous-espèce Éléphant d’Afrique barrir

sous-espèce Éléphant barrir

Si corpulence « importante » alorsSi sous-espèce égale à "Afrique"alors son émis fort et « plus grave »sinon si sous-espèce égale à "Asie"…Sinon si corpulence « moyenne »…

JavaC+ Livre Page 21 Lundi, 20. juillet 2009 8:50 08

Page 18: L’approche orientée objet

22 ◆ Conception orientée objet en Java et C++

pour enfants en langage C ? Pourquoi s’embarrasser de Java et C++ avec ces mécanis-mes objet relativement complexes ? Seule la recherche d’une qualité accrue du logicielde jeu motive et justifie la démarche. Encore une fois, aujourd’hui des milliards delignes de code sont dupliquées et donc maintenues « à perte ». Ces blocs de code sontde fait non réutilisables et instables par leur maintenance répétitive ; cela est sourcede bogues.

1.4.4 Héritage multiple

L’héritage mécanisé dans Java et C++ n’est finalement que le moyen de créer des caté-gories et de les mettre en rapport comme le font de nombreuses sciences :

• classifier les animaux en zoologie ;

• former des classes d’individus en statistiques ;

• répertorier, apparier ou grouper les pathologies et leurs variantes en médecine ;

• etc.

Dans ce cadre, l’héritage multiple se matérialise par une classe ayant plus d’uneascendante directe. On retrouve ce phénomène dans le règne animal à traversl’hermaphrodisme (voir figure 1.17). Au-delà, sur un plan plus technique informati-que, il existe de nombreuses situations où l’héritage multiple semble utile. Puisquel’héritage transfère attributs et fonctions vers une classe héritant d’une autre, il estévident (et parfois opportun) qu’une classe héritant de plusieurs gagne rapidementen fonctionnalités : une classe Image sonorisée peut ainsi hériter directement d’uneclasse Son et d’une classe Image par exemple (voir figure 1.17).

Malgré une apparente simplicité de la notion même d’héritage multiple et son carac-tère somme toute intuitif, sa gestion dans les langages de programmation par objetsest complexe. Au mieux, les programmeurs échouent souvent à maîtriser cettenotion. Au pire, ils créent des dégâts considérables quant à la lisibilité et la compré-hensibilité des programmes.

Un problème récurrent de l’héritage multiple est ainsi la fusion des champs dans laclasse héritière et le doublement éventuel de certaines opérations. Par exemple,la classe Image sonorisée à la figure 1.17 peut récupérer un attribut appelé sourceprovenant à la fois de la classe Image et de la classe Son. Dans le cas présent, il sembleévident que dans la classe Image sonorisée ces deux attributs, bien qu’ayant le même

Figure 1.17 • Héritage multiple.

Être vivant mâle

Être vivant hermaphrodite Image sonorisée

Être vivant femelle Image Son

JavaC+ Livre Page 22 Lundi, 20. juillet 2009 8:50 08

Page 19: L’approche orientée objet

L’approche orientée objet ◆ 23

nom (conflit dans leur usage donc), restent nécessaires si l’on souhaite accéder à deuxsources multimédias différentes. Pour une opération, c’est généralement plus ambigucar il arrive qu’une même opération héritée deux fois soit sémantiquement « la mêmechose ». Par exemple, une opération compresser doit être fusionnée dans la classeImage sonorisée si elle ne correspond pas à des algorithmes de compression distincts.Malheureusement, au moment de l’appel de compresser sur une instance d’Image sono-risée, Java et C++ exigent de savoir si l’on doit exécuter l’opération héritée d’Image oucelle héritée de Son.

La situation se complique en cas d’héritage répété (voir figure 1.18). En fait, Image etSon ont structurellement chacune le champ source. Image sonorisée le possède struc-turellement deux fois mais cela résulte d’une répétition : Image sonorisée hérite indi-rectement deux fois d’Objet multimédia. Techniquement, aucune solution ne peutêtre élaborée de façon automatique : disposer d’un seul ou de deux champs sourcedans Image sonorisée. Il est donc nécessaire de donner au programmeur une certainelatitude qui, au cas par cas, lui permet de choisir des fusions de champs ou de conser-ver des duplications. C’est l’héritage dit « virtuel » (mot-clé virtual) de C++ (voiraussi section 5.3.3).

Ce qui est vrai pour les attributs est aussi vrai pour les opérations. Ainsi, à grandeéchelle, si les hiérarchies d’héritage sont profondes et que certaines classes héritentnon pas de deux mais de trois, quatre… classes différentes, il apparaît un imbrogliodont il est difficile de se sortir. Une telle approche est intolérable car elle nuit à lacompréhension du code et donc à la maintenabilité.

Bien que Java ne prenne pas en charge l’héritage multiple entre classes, il l’autoriseentre les interfaces et donc des conflits peuvent surgir, tout comme en C++.Ce dernier ne supporte pas la notion d’interface comme Java, mais tolère l’héri-tage multiple entre classes. Aux sections 5.3.3 et 6.1.4, le lecteur peut trouver lestechniques précises à mettre en œuvre, respectivement en C++ et en Java, pours’en sortir.

Figure 1.18 • Héritage multiple et répété.

Image sonorisée

Image Son

Objet multimédia

source…

compresser…

JavaC+ Livre Page 23 Lundi, 20. juillet 2009 8:50 08

Page 20: L’approche orientée objet

24 ◆ Conception orientée objet en Java et C++

1.5 Généricité

L’héritage apparaît clairement comme un moyen de réduire la quantité de code écritet surtout de faire inter-opérer de façon optimale du code réparti dans des structuresde données différentes mais appariées. Ce style de programmation est néanmoinsinsuffisant lorsqu’on veut écrire du code encore plus générique, c’est-à-dire réutilisa-ble au sein de contextes variés et parfois non connus a priori. L’idée est qu’unprogrammeur écrive du code manipulant un type X anonyme et que ce type soitremplaçable, bien plus tard souvent, par une classe Y écrite par un autre programmeur.C’est la notion de généricité.

1.5.1 Interface versus implémentation

L’approche objet permet de décrire l’interface d’un type (« le quoi »), c’est-à-dire unensemble de propriétés qui le caractérise sans anticiper la façon dont ces propriétéssont « réalisées » (« le comment »). Ce principe a été inventé dans le langage Ada en1983 et illustré par le principe de « compilation séparée » où les interfaces peuventêtre vérifiées syntaxiquement correctes alors que leurs implémentations, souventmultiples, sont provisoirement indéfinies. Alors que Java propose de façon native ceconcept d’interface (voir section 6.1), C++ ne propose rien sinon des moyens de lesimuler. De façon orthogonale, Java et C++ supportent la programmation générique(voir chapitre 3). En plus, Java offre des mécanismes permettant au programmeur demixer généricité, héritage et interfaces (voir section 3.3.4).

La programmation générique se base sur cette notion d’interface au sens où le type Xprécédent doit être a minima caractérisé par une interface : un ensemble de servicesofferts. La substitution de X par Y n’est alors possible qu’à la condition que Y soit« compatible » avec cette interface.

Pour illustrer notre propos, intéressons-nous au tri en informatique et imaginonsque le logiciel de jeu pour enfants fasse apparaître tous les éléphants dans une anima-tion à l’écran par ordre de grandeur. Il est inopportun d’associer les fonctions de tri àun éléphant car la nature de ce calcul, comparé à barrir en particulier, n’est évidem-ment pas limitée à la classe Éléphant. On voit bien ici que l’héritage et le polymor-phisme ne sont pas les outils adéquats pour le problème posé.

L’interface nécessaire au problème de tri posé, jouant le rôle de X en l’occurrence, estune interface que nous appelons Triable. La classe Éléphant joue elle le rôle de Y.Le troisième et dernier acteur est une classe générique Tri.

À la figure 1.19, on visualise deux dépendances. Il y a la relation de compatibilité de laclasse Éléphant avec l’interface Triable. Attention néanmoins à l’aspect de la flècheterminée par un triangle blanc : ce n’est pas une relation d’héritage car le trait est enpointillé. C’est une relation d’implémentation d’une classe vers une interface. Celasignifie que la classe Éléphant fournit une implémentation spécifique (fondée surla hauteur des éléphants en l’occurrence) de l’opération est inférieur à. On voit à lafigure 1.19 que l’interface Triable est caractérisée par une fonction de comparaisonbooléenne (est inférieur à) et qu’Éléphant supporte et donc définit cette opération.

JavaC+ Livre Page 24 Lundi, 20. juillet 2009 8:50 08

Page 21: L’approche orientée objet

L’approche orientée objet ◆ 25

Le corps de cette opération exploite le champ hauteur qui se trouve justement dans laclasse Éléphant (voir figure 1.3). La seconde dépendance montre que la classegénérique Tri repose sur le fait qu’on ne peut trier que des objets de type Triable.Les instances d’Éléphant satisfont cette contrainte au regard de la premièredépendance.

1.5.2 Composants logicielsLa volonté de rendre la classe Tri hautement générique lui donne le statut de compo-sant logiciel, c’est-à-dire une classe aisément « composable » avec d’autres, et cela defaçon souvent différée. La classe Tri est en effet bâtie bien avant la classe Éléphant : aumoment où la classe Tri est construite, aucune connaissance de la classe Éléphantn’existe ; le projet de jeu d’éveil ayant présidé à la naissance de la classe Éléphant estinconnu. On est donc capable de faire inter-opérer des codes de programmeurs ne seconnaissant pas et écrits à des moments totalement différents pour des raisons ellesaussi complètement différentes. Le code générique du composant logiciel Tri estainsi :

•Triable t1,t2;•Si t1.est inférieur à(t2) alors ranger t1 avant t2 sinon…

La nature d’un composant logiciel est sa faculté à être configuré, ce qui correspond icià la substitution de Triable par Éléphant. On aboutit donc à :

•Éléphant t1,t2;•Si t1.est inférieur à(t2) alors ranger t1 avant t2 sinon…

L’adaptation de ce test pour les éléphants aboutit à un test plus spécialisé :

•Si t1.hauteur < t2.hauteur alors on range t1 avant t2 en fonction de•leur hauteur respective sinon…

Il est souvent regrettable que les programmeurs réinventent la roue chaque fois qu’ilsprogramment ; le fameux syndrome du NIH (Not Invented Here). La possibilité de

Figure 1.19 • Éléments d’un programme fondé sur la généricité.

Tri- éléments à trier : collection de Triable

« interface »Triable

+ trier…

Éléphant…

+ barrir+ est inférieur à(autre : Triable) : Boolean

+ est inférieur à(autre : Triable) : Boolean…

JavaC+ Livre Page 25 Lundi, 20. juillet 2009 8:50 08

Page 22: L’approche orientée objet

26 ◆ Conception orientée objet en Java et C++

fabriquer des composants logiciels est donc un atout d’un langage si l’on veut éviterpar exemple de reprogrammer ad vitam æternam le tri. Java et C++ disposent d’unesyntaxe intégrée pour dire que le composant logiciel Tri est paramétré par l’interfaceTriable et que cette dernière a vocation à être remplacée par une classe utilisateurcomme Éléphant.

La programmation par composants logiciels correspond donc finalement à la fabrica-tion de bibliothèques de code, par opposition à la réalisation d’applications orientéesutilisateurs finaux. Toutes les classes n’ont donc pas vocation à être génériques car lecoût de conception de ces dernières est plus élevé. En revanche, il n’y a pas à hésiter àintensivement réutiliser des composants logiciels que ce soit sous forme de codeouvert, sous forme de code binaire ou encore de code intermédiaire interprétable,comme cela est fait en Java.

Java et C++ proposent ainsi sous forme de composants logiciels rangés dans desbibliothèques tous les modèles connus de structures de données, dont les collections,c’est-à-dire les tableaux, les listes, les files, les ensembles, les tables de hachage… (voirchapitre 4). La figure 1.20 illustre ce principe.

Conjugués, encapsulation, héritage/polymorphisme et généricité sont des moyenspuissants de la réutilisation. Les programmeurs oublient souvent que l’orientéobjet est avant tout une technologie au service d’objectifs économiques. La réuti-lisation bien évidemment y participe pour diminuer le prix de revient desprogrammes.

Figure 1.20 • Réutilisation de composants logiciels préfabriqués.

Tableau

Bibliothèque(s) de composants logiciels Classes métier orientées « application »

Réutilisation(s)

Paramètre(s) de config.

Liste

Paramètre(s) de config.

File

Paramètre(s) de config.

Ensemble

Etc.

Paramètre(s) de config.

Table dehachage

Etc.

Paramètre(s) de config.

Jeu d’éveil pour les enfants

Éléphant

JavaC+ Livre Page 26 Lundi, 20. juillet 2009 8:50 08

Page 23: L’approche orientée objet

L’approche orientée objet ◆ 27

1.6 Programmation défensive

La programmation défensive est l’introduction dans les langages de programmationde mécanismes de gestion des pannes prévues, voire imprévues. Ces mécanismes sontassociés à une syntaxe dédiée. Les pannes peuvent venir de mauvais usages, de boguesrésiduels ou de contextes d’exécution instables. Un programme utilise en effet desressources, dont trois incontournables : le processeur, la mémoire et, avec Internetqui inonde notre quotidien, le réseau. L’émergence de l’informatique ubiquitaire(ubiquitous computing ou pervasive computing en anglais) fait que les systèmes logi-ciels s’exécutent dans des environnements fortement répartis, hautement partagés,changeants, instables voire évanescents (notion de disappearing computing enanglais). En termes de conception, il est difficile pour le programmeur d’anticiper cesconditions via des variations sur son code :

•Si mon code tourne sur un téléphone portable alors économiser la batterie•et la mémoire sinon s’il tourne sur…

Toutes ces préoccupations sont des préoccupations de qualité de service. Elles deviennentde plus en plus prépondérantes dans l’informatique moderne.

Ainsi, parmi les nombreuses qualités attendues d’un logiciel (facilité d’utilisation,rapidité, portabilité…), il en est une primordiale voire non négociable : la sûreté defonctionnement1. Les termes « robustesse » ou « fiabilité » sont aussi parfois utiliséscomme synonymes. Si un logiciel d’éveil pour les enfants tombe ainsi souvent enpanne, il est évident que l’enfant qui joue se lassera vite et abandonnera à jamais lejeu. Pour un logiciel critique embarqué dans un ascenseur, une automobile, un avionou encore une fusée, les bogues n’ont pas les mêmes conséquences : une mortd’homme est possible, d’où des enjeux d’une autre envergure que « satisfaire lesclients ».

L’orienté objet a, dès ses origines, eu un positionnement fort sur le sujet de la sûretéde fonctionnement. L’encapsulation, notamment (voir section 1.2) par la protectiond’altérations indésirables des données (les fameux effets de bord), est un vecteur de larobustesse. De manière plus générale, l’approche par objets/composants/servicespermet d’isoler les grandes actions d’un programme et ainsi de contractualiser lesusages « corrects » entre ces objets/composants/services.

L’explosion du premier vol de la fusée Ariane 5 est due à un bogue qui aurait pu êtreévité via la conception par contrats [Jézéquel et Meyer, 1997]. C’est ce type deconception objet qui permet d’exprimer des usages « corrects » entre entités logiciel-les et, par voie de conséquence, d’établir par différence les comportements anormauxdevant mener à des stratégies de défense des programmes.

Un programmeur Java et C++ doit donc imaginer dans quelles conditions il peutterminer son programme (normalement ou anormalement), lui faire subir un modedégradé, lui faire recouvrer un état libre d’erreurs et, plus globalement, l’adapter à unenvironnement d’exécution perturbé.

1. Le lecteur prendra la précaution de faire la différence avec la notion de sécurité, qui recouvre plusvolontiers la protection des programmes contre des attaques délibérées d’autres programmes.

JavaC+ Livre Page 27 Lundi, 20. juillet 2009 8:50 08

Page 24: L’approche orientée objet

28 ◆ Conception orientée objet en Java et C++

1.6.1 Conception par contrats

La conception par contrats permet d’intégrer dans les programmes les conditions etrègles (contrats) sous lesquelles les programmes fonctionnent correctement, et, pardifférence, celles génératrices d’anomalies. En cas de rupture de contrat, leprogramme est censé se défendre via des stratégies idoines, qui peuvent reposer surdes ressources alternatives et/ou supplémentaires. C’est le cas d’un objet source son bisdans l’exemple qui suit. Les contrats sont des expressions logiques qui sont vraies sitout va bien, et fausses dans le cas contraire.

À la figure 1.5, la classe Éléphant est dotée d’un attribut technique source son de typeFichier son qui correspond à un barrissement pour traitement par le périphériqueaudio de l’environnement où s’exécute le logiciel d’éveil. La réussite de l’opérationbarrir résulte bien évidemment de la disponibilité (pilote logiciel) et d’un bon état demarche continu du périphérique audio. Concrètement, toute instance en fonctionne-ment de la classe Éléphant dispose de deux objets locaux, transparents (idéalement)du point de vue de l’utilisateur de l’opération barrir : l’objet source son et un objetport audio.

Plusieurs choix d’implémentation s’offrent au programmeur de la classe Éléphant.À la création d’une instance d’Éléphant, source son et port audio sont mis en état demarche et vérifiés prêts à fonctionner. Par exemple, source son est « ouvert » (opéra-tion commune sur un fichier). Alternativement, source son et port audio sont mis enétat de marche et vérifiés prêts à fonctionner à chaque usage puis mis au repos(« libérés ») dès que l’on n’en a plus besoin. Une autre possibilité est que port audion’ait qu’un accès restreint au périphérique audio pour le programmeur de la classeÉléphant. Ainsi, aucune action « lourde » (son ouverture et sa fermeture en l’occur-rence) n’est possible sur cet objet. Il y a encore d’autres solutions d’implémentation.

Des clauses de « bon » fonctionnement sont donc associées à chaque choix d’implé-mentation. Dans le premier type d’implémentation, on conçoit la fonction barrir ens’appuyant sur un « invariant », c’est-à-dire une assertion qui doit être vraie tout aulong de la vie d’une instance de la classe Éléphant : fichier source son OK (c’est-à-direouvert…) et port audio OK (ouvert…).

La conception par contrats s’intéresse à trois types de clauses importantes : les inva-riants, les pré-conditions et les post-conditions. Les invariants sont vrais tout au longde la vie des instances alors que les pré-conditions et les post-conditions sont asso-ciées aux fonctions. Les pré-conditions d’une fonction doivent être vraies préalable-ment à son appel alors que les post-conditions le doivent à l’issue de son exécution.Une approche formelle (fondée sur les mathématiques) permet d’exprimer toutes cesclauses : ce sont les types abstraits de données (voir section 3.2).

Au vu de défaillances possibles, un objet de type Fichier audio appelé source son bis estdonc introduit. C’est sur la base de cet objet qu’est élaborée une stratégie de défense.

À la figure 1.21, la fonction barrir s’enrichit d’un paramètre (source son bis) de typeFichier audio selon la logique suivante : si une valeur est passée pour ce paramètre,l’utiliser, sinon si ce paramètre possède la valeur null en Java ou NULL en C++, utiliser lavariable source son locale.

JavaC+ Livre Page 28 Lundi, 20. juillet 2009 8:50 08

Page 25: L’approche orientée objet

L’approche orientée objet ◆ 29

Une question pragmatique est : est-ce que le fichier source son bis est passé déjà ouvertet prêt à fonctionner ? Si oui, la pré-condition à la fonction barrir est : source son bisOK (c’est-à-dire ouvert…). Une autre question non négligeable est : dans quel étatest rendu l’objet source son bis ? Si la réponse est « il doit rester en l’état » alors la post-condition de la fonction barrir est aussi : source son bis OK (c’est-à-dire ouvert…).Ces clauses sont résumées à la figure 1.21 et placées entre {}.

Le lien entre sûreté de fonctionnement et conception par contrats est évident etimmédiat : qu’en est-il si les invariants, pré-conditions ou post-conditions sontviolés ? Java et C++ donnent la possibilité d’écrire des assertions logiques correspon-dantes, de les configurer de manière à dire au compilateur comment les gérer àl’exécution. Finalement, elles sont aussi un moyen de pallier les anomalies : c’est lagestion des exceptions.

Le contrat associé à un démarrage « correct » ou « normal » de la fonction barrir estdonc que l’invariant de la classe Éléphant soit vrai et que la pré-condition de la fonc-tion barrir soit aussi vraie, à savoir :

•{source son OK, c’est-à-dire ouvert…} ET {source son bis OK, c’est-à-dire•ouvert…}

La négation de cette proposition logique établit de fait une condition « incorrecte »ou « anormale » de fonctionnement, à savoir :

•NON {source son OK, c’est-à-dire ouvert…} OU NON{source son bis OK,•c’est-à-dire ouvert…}

Java et C++ offrent la possibilité à l’aide du littéral assert (voir section 7.1.6 pour plusde détails) d’écrire des assertions logiques à différents endroits d’un programme. C’estune façon limitée d’exprimer des contrats en Java et C++ car assert n’est pas un supportcomplet de la conception par contrats. Seul le langage de programmation par objetsEiffel [Meyer, 1997] propose un support riche et formel pour la conception par contrats.

1.6.2 Stratégie de défense

Si la conception par contrats aide à baliser proprement un programme, elle ne dit pascomment, où et quand réagir aux problèmes si les choses se passent mal.

Figure 1.21 • Contrats d’usage de la classe Éléphant (invariants) et de la fonction barrir (pré-conditions et post-conditions).

Éléphant {source son OK, c’est-à-dire ouvert…}

+ hauteur- source son : Fichier audio

{source son bis OK, c’est-à-dire ouvert…} + barrir(source son bis : Fichier audio) {source son bis OK, c’est-à-dire ouvert…}…

JavaC+ Livre Page 29 Lundi, 20. juillet 2009 8:50 08

Page 26: L’approche orientée objet

30 ◆ Conception orientée objet en Java et C++

Imaginons que la pré-condition de la fonction barrir soit violée, c’est-à-dire que sonargument source son bis ne soit pas ouvert. Un correctif dans le corps de la fonctionbarrir pourrait alors être de tenter une ouverture. Est-ce possible ou tout simplementpermis de le faire vu que la logique du contrat est justement que la fonction barrirn’est censée lire des données son qu’à partir de cet objet source son bis ? Si l’on indiquel’objet source son bis constant en C++, c’est-à-dire « en lecture seule » (mot-clé const),cela empêche de fait la tentative d’ouverture. Par définition, un objet constant ne peutpas changer d’état en C++.

Il en résulte qu’au sein de la fonction barrir les possibilités de remise en ordre sontlimitées. C’est souhaitable en fait car cette variable source son bis ne lui appartient quepeu et temporairement.

Une stratégie de défense canonique est donc de corriger un problème (1) si l’ondispose des attributs et des moyens pour le faire. Cela donne dans notre exemple :

•Si NON{source son bis OK, c’est-à-dire ouvert…} alors tenter une ouverture…

D’un autre côté, si l’on ne dispose pas des attributs et/ou des moyens pour corrigerun problème (2), on ne fait rien, mais néanmoins on le signale :

•Si NON{source son bis OK, c’est-à-dire ouvert…} alors informer le contexte•appelant, c’est-à-dire l’objet appelant la fonction barrir, du problème ;•lui communiquer des données (éventuellement) relatives à ce problème…

Finalement, si l’on s’est engagé dans une tentative de correction mais que cette tenta-tive échoue, alors on le signale aussi avec, éventuellement, des données sur les raisonsde l’échec :

•Si NON{source son bis OK, c’est-à-dire ouvert…} alors tenter une ouverture…•Si échec alors informer le contexte appelant…

Ces enchaînements font apparaître une coordination équilibrée entre le contexteappelant et l’appelé (la fonction barrir d’un objet Éléphant donné). Si le contexte appe-lant est informé d’un problème survenu dans l’appelé, il lui suffit de réitérer le proto-cole de défense exposé précédemment avec son propre appelant, cela de façonrécursive. Le programme principal (la fameuse fonction main de Java et C++) restealors l’ultime niveau de défense. La qualité de ce protocole de défense réside dans lefait que chaque niveau plus externe dispose a priori de moyens supplémentairesd’analyse d’un problème et d’un plus grand nombre de possibilités pour le corriger.Dans notre exemple, la remontée de l’anomalie doit logiquement (solution (2) précé-demment) aboutir à l’objet ayant créé l’objet source son bis et donc à même de procéderà des interventions lourdes le concernant : forcer son ouverture en l’occurrence.

Toute cette démarche permet d’éviter des interruptions brutales d’un programme oùle code appelle de façon irraisonnée et irrémédiable une fonction de sortie (une autrefameuse fonction de Java et C++ : exit). Au-delà, un diagnostic doit être établi pourdéterminer si le programme peut reprendre « normalement », si un mode dégradés’impose provisoirement ou définitivement (l’anomalie n’a pas été corrigée mais desfonctionnements substitutifs contournent cette anomalie), si l’arrêt définitif duprogramme est inévitable, etc.

JavaC+ Livre Page 30 Lundi, 20. juillet 2009 8:50 08

Page 27: L’approche orientée objet

L’approche orientée objet ◆ 31

Dans l’esprit de la conception par contrats, les corrections qui font suite à la violationdes pré-conditions sont plus volontiers assignées aux appelants alors que les post-conditions sont plus du ressort des appelés. Restent les invariants plus difficiles àcontrôler (en Java et C++ en particulier, comparativement à Eiffel) et les erreurssystème non provoquées par le programme mais par son environnement tant logiciel(autre programme, système d’exploitation…) que matériel (déconnexion d’un câbleréseau, panne physique d’un périphérique…).

1.6.3 Objets exceptions

Les exceptions et leur gestion sont les supports offerts par des langages à objetscomme C++ et Java pour équiper les programmes de stratégies de défense.

Les exceptions sont en programmation objet des objets à part entière, c’est-à-dire desinstances de classes. Un objet exception peut être vu comme l’incarnation d’unproblème plus ou moins grave s’étant produit dans un objet en train de s’exécuter.Il a optionnellement des champs représentant des informations sur la nature duproblème. Par exemple, un débordement de format numérique (overflow en anglais),comme cela s’est passé pour Ariane 5, peut faire appel à un champ dans la classe del’exception de manière à communiquer la valeur de la variable ayant causé l’overflow.

Le signalement d’un problème dans l’esprit de la section précédente est donc la créa-tion d’un objet exception et « sa levée » (c’est-à-dire son envoi, son émission) asso-ciée au mot-clé throw de Java et C++. Le pendant de cette action est la capture d’uneexception (mots-clés try et catch de Java et C++). La section 7.1 traite et illustre lagestion des exceptions en Java et en C++.

À la figure 1.22, chaque fois qu’une anomalie apparaît quant aux usages de la classeÉléphant, le programmeur de cette même classe est chargé de matérialiser cetteanomalie par un objet de type Éléphant exception qu’il crée, puis renseigne et finale-ment signale de manière à enclencher le processus de défense et de correctionéventuelle (voir tableau 1.1).

Le tableau 1.1 fait apparaître trois rôles (colonnes) : un objet (appelant de l’appelant)appelle un autre objet (appelant) qui appelle la fonction barrir sur un objet de typeÉléphant. Chaque ligne du tableau est une phase du processus. De haut en bas, onvoit comment le programme évolue si la fonction barrir se déroule mal au début.

Figure 1.22 • Classe matérialisant un type d’exception propre aux problèmes potentiellement soulevés par la classe Éléphant et ses usages.

Éléphant exception + explication du problème : String

- source son défaillante : Fichier audio…

JavaC+ Livre Page 31 Lundi, 20. juillet 2009 8:50 08

Page 28: L’approche orientée objet

32 ◆ Conception orientée objet en Java et C++

Le tableau donne aussi des alternatives si l’appelant gère seul le problème ou s’ildemande de l’aide à son propre appelant.

Le scénario décrit au tableau 1.1 montre que les tactiques de défense peuvent s’affinerau niveau de l’appelant de la fonction barrir pour Babar. Corriger l’anomalie (a) ou

Tableau 1.1 • Scénario classique (et variantes) de gestion d’exception

Appelant de l’appelant Appelant Appelé : Babar

O------------------------> Appeler la fonction barrir(source son bis) sur Babar

O------------------------>

Si NON{source son bis OK, c’est-à-dire ouvert…} alors informer le contexte appelant :

Créer une instance ee d’Éléphant exception

Écrire le champ explication du problème de ee avec "le fichier son passé en paramètre ne fonctionne pas…"

Écrire le champ source son défaillante de ee avec source son bis

(a) Capturer ee

Réparer source son bis

Appeler la fonction barrir(source son bis) sur Babar

O------------------------>

(a’) ou (b) ou (c) en collaboration avec l’appelant de l’appelant de l’appelant

(a’) = variation de (a) sur le 3e point, à savoir : ordonner à l’appelant de tenter à nouveau d’appeler barrir sur Babar

(b) Capturer ee

Réparer partiellement source son bis

Router ee au contexte appelant

<------------------------O

(a’) ou (b) ou (c) en collaboration avec l’appelant de l’appelant de l’appelant

(a’) = variation de (a) sur le 3e point, à savoir : ordonner à l’appelant de tenter à nouveau d’appeler barrir sur Babar

(c) Ignorer ee

<------------------------O

JavaC+ Livre Page 32 Lundi, 20. juillet 2009 8:50 08

Page 29: L’approche orientée objet

L’approche orientée objet ◆ 33

alors la capturer et ne la corriger que partiellement (par manque de moyens et/oud’outils d’analyse). Dans ce second cas (b), un routage explicite vers l’appelant del’appelant se produit de manière à parfaire la correction. La troisième et dernièreversion (c) est le fait d’ignorer l’exception. Le routage est néanmoins implicite dansles langages à objets comme Java et C++. Charge au programme dans sa globalitéd’établir quelque part un gestionnaire du problème de type Éléphant exception pouréviter que l’anomalie se propage jusqu’au programme principal.

1.6.4 Types d’exceptions prédéfinies

Aux exceptions types d’une classe utilisateur ou d’une application particulière,s’ajoutent les erreurs récurrentes que rencontre tout logiciel en fonctionnement. Bienévidemment, Java, C+ ou d’autres offrent dans leurs bibliothèques tout un panel declasses d’exceptions prédéfinies, liées par héritage le plus souvent vu qu’elles ne sontjamais que des classes comme les autres. On voit ici l’intérêt de pousser à la réutilisa-tion au-delà des classes « fonctionnelles » pour toucher celles en charge de la sûretéde fonctionnement.

Un problème d’allocation mémoire est un phénomène connu et du domaine dupossible. Il est donc naturel de l’incarner par une classe. Cette classe est haute-ment réutilisable. En termes plus abstraits, on pourrait imaginer une classeErreur_materielle racine d’un graphe d’héritage des sous-types d’erreurs matériellespossibles.

De façon illusoire, le programmeur peut croire que, via cette classe, il est en mesurede gérer les erreurs matérielles ; mais où placer le code de capture de ce typed’exception ? Partout ? C’est irréaliste. De manière générale, les erreurs système étantaléatoires et sans dépendance formelle avec celles que peut naturellement provoquerun programme, il est difficile de développer des stratégies de défense parfaites. Le testreste un complément indispensable à la gestion des exceptions et tout logiciel n’est doncjamais infaillible.

Le cas Ariane 5 est typique au sens où les ingénieurs avaient certifié par le test quel’anomalie (un simple overflow rappelons-le) ayant provoqué l’explosion en vol nepouvait pas arriver au regard des usages qui seraient faits. En clair, la variable àl’origine de l’overflow « ne pouvait pas » en toutes circonstances contenir un nombre« trop grand ». Les tests décrivant ces usages étaient manifestement incomplets.

L’autre aspect est que même si un programme détecte avec efficacité les erreursd’overflow, les erreurs mémoire, les erreurs matérielles… y a-t-il toujours possibilitéde corrections ? Il n’y en a évidemment pas toujours. Dans les systèmes les plus criti-ques, d’autres programmes prennent le relais du programme défaillant, voirel’homme dans les cas extrêmes.

1.6.5 Résumé sur la programmation défensive

La programmation défensive sous-tend la construction de deux programmes« virtuels » : celui en charge du fonctionnel, qui fait les calculs attendus, et celui encharge du contrôle et du dépannage s’il y a lieu. En outre, ces deux programmes

JavaC+ Livre Page 33 Lundi, 20. juillet 2009 8:50 08

Page 30: L’approche orientée objet

34 ◆ Conception orientée objet en Java et C++

s’imbriquent car ils sont fondus dans les classes Java et C++, qui offrent des syntaxesdifférenciées pour le fonctionnel et la gestion des exceptions. Ce style de programma-tion n’est pas naturel pour qui vient d’un autre monde que la programmation objet.L’approche objet par sa forte modularité favorise néanmoins la localisation desdéfauts probables d’exécution et garantit une répartition des tâches de correctionplus appropriée.

1.7 Bilan

L’orientation objet, avant d’être une approche de programmation, est une approchede modélisation : il s’agit de structurer les programmes différemment pour en accroî-tre leur qualité. Cette qualité a un impact sur les coûts de fabrication des logiciels :fabrication plus rapide par réutilisation, évolution (adaptation à l’évolution desbesoins des utilisateurs et réparation des bogues) facilitée et maîtrisée dans le temps,résilience aux pannes. Les principes (encapsulation, héritage, généricité…) sous-jacents à l’orienté objet ne sont pas simples à comprendre et à manipuler, mais ne pass’engager dans cette voie et ne pas y adhérer est une régression.

Java et C++ sont des supports de tous les mécanismes exposés dans ce chapitre.Au-delà de la difficulté de maîtriser la syntaxe de chacun, il est fondamental de nejamais oublier que l’application des principes de ce chapitre est cruciale.

1.8 Bibliographie

Barbier F., UML 2 et MDE – Ingénierie des modèles avec études de cas, Dunod, 2005.

Barnes D., Kölling M., Conception Objet en Java avec BlueJ 2e édition – Une approcheinteractive, Pearson Education, 2005.

Jézéquel J.-M., Meyer B., « Design by Contract : The Lessons of Ariane », IEEEComputer, vol. 30, n˚ 2, pp. 129-130, 1997.

Meyer B., Object-Oriented Software Construction Second Edition, Prentice Hall, 1997.

1.9 Exercices

Les exercices proposés ici ont leur solution sur le Web à l’adresse www.FranckBar-bier.com ou directement à l’adresse www.PauWare.com/Java_C++.

Exercice 1

Conceptualisez la notion de voiture en tant que classe selon trois points de vuedifférents, celui du conducteur, celui de l’agent réparateur et celui de l’assureur.Pour cela, affectez aux trois classes correspondant à chaque point de vue tout oupartie des propriétés suivantes (n’utilisez pas l’héritage dans cet exercice) : moteur,volant, démarrer, largeur, estimer la valeur vénale, prendre un virage, n˚immatricula-tion, vidanger l’huile, hauteur, kilométrage, reculer, gonfler les pneus, poids, nettoyer

JavaC+ Livre Page 34 Lundi, 20. juillet 2009 8:50 08

Page 31: L’approche orientée objet

L’approche orientée objet ◆ 35

l’extérieur, longueur, valeur argus, nombre de chevaux DIN, nettoyer l’intérieur, experti-ser en vue réparation, nombre de chevaux fiscaux. Classez ces propriétés soit commeattribut soit comme opération, donnez une visibilité et dites pour chaque opérationles attributs qu’elle est susceptible d’utiliser.

Exercice 2

Trouvez pour chacune des classes qui suivent une propriété de classe (un attributou une fonction) appropriée :

• Calendrier ;

• Thermomètre médical ;

• Faisceau de lumière ;

• Son ;

• Cercle.

Exercice 3

Inspirez-vous du mécanisme de généricité en proposant une conception desnotions de LIFO (Last In, First Out) et FIFO (First In, First Out). Appliquez ces deuxnotions à un troupeau d’éléphants que l’on parquerait dans une zone protégée.Indication : intéressez-vous à une interface supportant les opérations in et out.

Exercice 4

Établissez une hiérarchie d’héritage avec les espèces suivantes : Oiseau, Animal,Cormoran, Pingouin, Aigle, Ovipare, Flamant rose et Fou de Bassan. Dans cette hiérarchie,placez les fonctions voler et nager sous l’eau.

Exercice 5

À propos de la gestion des exceptions, cochez les propositions qui sont vraies :

• Une exception se produisant lors du dysfonctionnement d’une instruction doitimpérativement et toujours être capturée.

• Une exception peut être capturée sans donner lieu à un traitement de réparationet être ensuite routée.

• Une exception peut toujours faire l’objet d’une réparation.

• Une exception doit toujours faire l’objet d’une réparation.

• Une exception capturée, associée ou non à un processus de réparation, interromptquoi qu’il en soit un programme.

JavaC+ Livre Page 35 Lundi, 20. juillet 2009 8:50 08