Chapitre 519 minIntermédiaire
Héritage, agrégation, composition, interface et classe-association
Par Josselyn C., prof de BTS SIO. Mis à jour le .
À la fin de ce chapitre, tu sauras
- modéliser un héritage, une classe abstraite et une interface ;
- choisir entre association, agrégation et composition, et le justifier ;
- utiliser une classe-association pour une association qui porte des données ;
- traduire un MCD Merise en diagramme de classes sans inverser les multiplicités.
4 exercices corrigés3 schémas
Au-delà de l'association simple, le diagramme de classes UML dispose de relations spécialisées : l'héritage (ou généralisation) pour dire qu'une classe « est une sorte de » une autre, l'agrégation et la composition pour dire qu'un objet « est fait de » parties, la réalisation d'une interface, la dépendance, et la classe-association pour une association qui porte des données. Ce chapitre explique chacune, comment choisir entre elles, ce qui change par rapport au MCD de Merise, et se termine par le diagramme de classes complet d'AppliFrais.
L'héritage (généralisation)
La généralisation relie une classe spécialisée (la classe fille, ou sous-classe) à une classe plus générale (la classe mère, ou super-classe). Elle se dessine par un trait plein terminé par un triangle creux, la pointe du côté de la classe mère. Elle se lit « est une sorte de » : un visiteur est un utilisateur, un comptable est un utilisateur.
La classe fille hérite de tout ce que contient la classe mère : ses attributs, ses opérations et ses associations. On ne les redessine pas dans la fille ; on n'y écrit que ce qu'elle ajoute (dateEmbauche pour le visiteur) ou ce qu'elle redéfinit (une opération répétée dans la fille, avec la même signature, est une redéfinition).
Plusieurs classes filles d'une même mère se dessinent souvent avec des triangles fusionnés en un seul, d'où partent les traits vers chaque fille. On peut préciser l'ensemble par une contrainte : {complete} (toute instance de la mère appartient à une fille), {incomplete}, {disjoint} (aucune instance n'appartient à deux filles), {overlapping}. Utilisateur, Visiteur et Comptable forment un ensemble {complete, disjoint} : tout utilisateur est soit visiteur, soit comptable.
UML autorise l'héritage multiple (une classe avec deux mères), mais C#, Java et PHP ne le permettent pas pour les classes. Dans un diagramme de conception destiné à ces langages, évite-le : les interfaces servent à cela.
Classes abstraites
Une classe abstraite est une classe dont on ne crée jamais d'objet directement : elle sert de base à ses filles. Son nom s'écrit en italique, ou suivi de la contrainte {abstract} (plus lisible sur une copie manuscrite). Une opération abstraite, en italique elle aussi, n'a pas de code dans la classe mère : chaque fille concrète doit la redéfinir.
Dans AppliFrais, on ne crée jamais un « utilisateur » tout court : Utilisateur est abstraite. Son opération Role() : string est abstraite : un visiteur répond « visiteur médical », un comptable « comptable ». Le code qui affiche le rôle de n'importe quel utilisateur appelle Role() sans savoir à quel type d'utilisateur il a affaire : c'est le polymorphisme, détaillé dans le cours Programmation orientée objet (polymorphisme).
Interfaces et réalisation
Une interface est un contrat : une liste d'opérations, sans code, que toute classe qui la réalise s'engage à fournir. Elle se dessine comme une classe, avec le stéréotype «interface» au-dessus de son nom, et en général sans attributs. Par convention C#, son nom commence par un I : IExportable.
Une classe qui réalise (implémente) une interface lui est reliée par un trait pointillé terminé par un triangle creux vers l'interface. Une autre notation, dite « sucette » (lollipop), place un petit cercle relié à la classe, avec le nom de l'interface : on la retrouve dans le diagramme de composants.
Dans AppliFrais, la comptabilité exporte des données vers un tableur. IExportable déclare + VersCsv() : string ; FicheFrais la réalise. Le service d'export travaille avec une liste d'IExportable, sans connaître leur classe : le jour où il faudra exporter aussi les visiteurs, il suffira que Visiteur réalise IExportable.
| Classe abstraite | Interface | |
|---|---|---|
| Ce qu'elle exprime | « est un » : une famille d'objets | « sait faire » : une capacité |
| Attributs | Oui | Non (en pratique) |
| Code dans les opérations | Oui, sauf pour les opérations abstraites | Non (C# et Java permettent des méthodes par défaut, rarement utiles au BTS) |
| Combien par classe | Une seule classe mère (C#, Java, PHP) | Autant d'interfaces que voulu |
| Notation | Nom en italique ou {abstract}, triangle creux en trait plein | «interface», triangle creux en pointillé |
| Exemple AppliFrais | Utilisateur | IExportable |
La dépendance
Une dépendance est un lien faible et temporaire : une classe en utilise une autre ponctuellement, le temps d'une opération (en paramètre, en variable locale, en appel d'une méthode statique), sans garder de référence. Elle se dessine par une flèche pointillée à pointe ouverte, parfois avec le stéréotype «use».
Le service d'export dépend d'IExportable : il en reçoit une liste en paramètre, l'exporte et l'oublie. Si IExportable change, le service d'export risque de devoir changer : c'est tout ce que dit la flèche. On dessine peu de dépendances dans un diagramme de classes ; elles prennent tout leur sens dans un diagramme de paquetages.
Agrégation et composition
L'agrégation et la composition sont deux associations particulières qui expriment une relation tout-partie : « est fait de », « contient », « regroupe ».
- La composition (losange plein du côté du tout) est une appartenance forte : la partie n'appartient qu'à un seul tout à la fois, et elle n'existe pas sans lui. Si on supprime le tout, on supprime ses parties. Une ligne de frais hors forfait n'a aucun sens hors de sa fiche :
FicheFrais ◆──── LigneFraisHorsForfait. - L'agrégation (losange creux du côté du tout) est une appartenance faible : la partie peut exister seule, et parfois appartenir à plusieurs touts. Une région regroupe des visiteurs ; si GSB supprime une région, les visiteurs ne disparaissent pas, ils sont rattachés ailleurs :
Region ◇──── Visiteur.
Trois questions pour choisir :
- La partie peut-elle exister sans le tout ? Non → composition.
- La partie peut-elle appartenir à plusieurs touts en même temps, ou changer de tout ? Oui → pas une composition (agrégation ou association simple).
- Le tout est-il responsable de créer et de détruire ses parties ? Oui → composition.
| Association | Agrégation | Composition | |
|---|---|---|---|
| Symbole | Trait | Losange creux côté tout | Losange plein côté tout |
| Relation | Se connaissent | Tout-partie, faible | Tout-partie, forte |
| Durée de vie de la partie | Indépendante | Indépendante | Liée au tout |
| Partage de la partie | Possible | Possible | Impossible : un seul tout |
| Multiplicité côté tout | Quelconque | Quelconque | 1 ou 0..1 |
| Exemple | Fiche — Comptable (validateur) | Region ◇— Visiteur | FicheFrais ◆— LigneFraisHorsForfait |
La classe-association
Une association peut porter des données qui n'appartiennent à aucune des deux classes. Une fiche utilise plusieurs forfaits, et un forfait est utilisé par plusieurs fiches ; mais la quantité (18 étapes, 412 kilomètres) n'est ni une propriété de la fiche (elle en a quatre, une par forfait) ni du forfait (elle change à chaque fiche). Elle appartient au couple fiche-forfait.
UML la représente par une classe-association : une classe reliée par un trait pointillé au milieu de l'association. Elle a un nom, des attributs et éventuellement des opérations : LigneFraisForfait porte - quantite : int et + Montant() : decimal.
Par défaut, une classe-association n'autorise qu'un seul lien par couple d'objets : une fiche ne peut pas contenir deux fois le forfait « nuitée ». C'est exactement la règle voulue ici. Si un même couple devait pouvoir être relié plusieurs fois (un adhérent qui emprunte plusieurs fois le même livre, à des dates différentes), on transforme la classe-association en classe ordinaire reliée aux deux autres par deux associations : Adherent 1 ── 0..* Emprunt 0..* ── 1 Livre.
Énumérations, contraintes et notes
Une énumération est un type dont les valeurs possibles sont listées : on la dessine comme une classe, avec le stéréotype «enumeration», et ses valeurs dans le compartiment des attributs. L'état d'une fiche ne prend que cinq valeurs : «enumeration» EtatFiche avec Creee, Cloturee, Validee, MiseEnPaiement, Remboursee. L'attribut etat de FicheFrais est de type EtatFiche.
Une contrainte entre accolades précise une règle que la notation graphique ne sait pas dire :
{ordered}à l'extrémité0..*d'une association : les objets sont ordonnés (les lignes hors forfait, par date) ;{xor}entre deux associations : un objet est relié par l'une ou par l'autre, jamais les deux ;{montant > 0}près d'un attribut : une règle de gestion.
Une note (rectangle au coin replié, relié par un pointillé) porte une règle en français : « Le montant validé est la somme des lignes forfaitisées et des lignes hors forfait non refusées. » Sur une copie, c'est souvent la façon la plus claire d'exprimer une règle de gestion.
Ce qui change entre UML et Merise
Le MCD de Merise et le diagramme de classes se ressemblent et se traduisent l'un dans l'autre, mais ils n'ont pas le même but : le MCD décrit les données à stocker, le diagramme de classes décrit les classes d'un programme, avec leur comportement.
| Merise (MCD) | UML (diagramme de classes) |
|---|---|
| Entité | Classe |
| Propriété | Attribut, avec un type et une visibilité |
| Identifiant souligné | Pas d'équivalent : un objet est identifié par sa référence |
| Association binaire | Association |
| Cardinalités 0,1 / 1,1 / 0,n / 1,n, près de l'entité décrite | Multiplicités 0..1 / 1 / 0..* / 1..*, près de la classe comptée |
| Association porteuse de propriétés | Classe-association |
| Héritage (spécialisation) | Généralisation |
| Entité faible, identifiant relatif | Souvent une composition |
| Rien | Opérations, visibilité, navigabilité, classes abstraites, interfaces |
Le piège est la ligne des cardinalités : la règle « un visiteur remplit zéro ou plusieurs fiches ; une fiche est remplie par un seul visiteur » s'écrit VISITEUR 0,n ── REMPLIR ── 1,1 FICHE en Merise, et Visiteur "1" ──── "0..*" FicheFrais en UML. Les nombres sont les mêmes, chacun a changé de côté. Le cours Merise consacre un chapitre entier à ces correspondances, avec des exercices dans les deux sens : MCD et diagramme de classes UML.
Le diagramme de classes d'AppliFrais
Voici le diagramme de classes métier d'AppliFrais, qui rassemble tout ce qui précède. Pour qu'il tienne sur une page, seules les opérations métier principales de FicheFrais sont affichées ; le chapitre 6 donne la liste complète, avec le code.
Lis-le en phrases, comme un correcteur :
- un utilisateur est soit un visiteur, soit un comptable ; on ne crée jamais d'utilisateur « tout court » ;
- une région regroupe zéro ou plusieurs visiteurs ; un visiteur appartient à une seule région, et lui survit ;
- un visiteur remplit zéro ou plusieurs fiches ; une fiche est remplie par un seul visiteur ;
- une fiche est validée par zéro ou un comptable (zéro tant qu'elle n'est pas validée) ; un comptable a validé zéro ou plusieurs fiches ;
- une fiche est composée de zéro ou plusieurs lignes hors forfait, qui disparaissent avec elle ;
- une fiche utilise zéro ou plusieurs forfaits, avec pour chacun une quantité, portée par la ligne de frais forfaitisés ; au plus une ligne par forfait et par fiche ;
- une fiche sait s'exporter en CSV, parce qu'elle réalise
IExportable; - l'état d'une fiche est l'une des cinq valeurs de
EtatFiche; - les flèches ouvertes donnent la navigabilité : une fiche connaît son validateur, ses forfaits et ses lignes hors forfait, mais ni le comptable, ni le forfait, ni la ligne ne connaissent la fiche. Les autres associations se parcourent dans les deux sens.
Exercices
Exercice 1 sur 4 Quelle relation ? Réussi À revoir
Pour chaque couple, choisis entre association, agrégation, composition, généralisation, réalisation et dépendance, et justifie en une phrase.
FactureetLigneFacture.VoitureetVehicule.PlaylistetMorceau.MedecinetPatient(un médecin suit des patients).CommandeetIPayable(une commande peut être payée par le module de paiement).GenerateurPdfetFacture(le générateur reçoit une facture en paramètre et produit un fichier).ImmeubleetAppartement.
Voir la réponseMasquer la réponse de l'exercice 1
- Composition, losange plein côté
Facture: une ligne n'existe pas sans sa facture et n'appartient qu'à elle. - Généralisation : une voiture est un véhicule (triangle creux vers
Vehicule). - Agrégation : une playlist regroupe des morceaux, mais un morceau existe sans elle et figure dans plusieurs playlists. Une association simple est aussi acceptable.
- Association : ni l'un ni l'autre n'est une partie de l'autre ; ils se connaissent.
- Réalisation :
Commandeimplémente l'interfaceIPayable(pointillé et triangle creux versIPayable). - Dépendance : le générateur utilise la facture le temps d'une opération, sans garder de référence.
- Composition : un appartement n'existe pas sans son immeuble et n'appartient qu'à un seul. Un appartement ne change pas d'immeuble ; si l'immeuble est démoli, ses appartements aussi.
Tu avais trouvé ?
Exercice 2 sur 4 Abstraite ou interface ? Réussi À revoir
Une application de gestion de parc informatique manipule des postes fixes, des portables et des imprimantes. Tous ont un numéro d'inventaire, une date d'achat et un prix, et calculent leur valeur résiduelle ; chaque type la calcule différemment (amortissement sur 3 ans pour les portables, 5 ans pour le reste). Les portables et les imprimantes peuvent être géolocalisés par le service de sécurité, mais pas les postes fixes. Propose une conception.
Voir la réponseMasquer la réponse de l'exercice 2
- Une classe abstraite
Materiel {abstract}avec- numInventaire : string,- dateAchat : DateOnly,- prix : decimalet l'opération abstraite+ ValeurResiduelle(jour : DateOnly) : decimal {abstract}. Trois classes filles,PosteFixe,Portable,Imprimante, reliées àMaterielpar une généralisation, chacune redéfinissantValeurResiduelle. - Une interface
«interface» IGeolocalisableavec+ Position() : string, réalisée parPortableetImprimante(pointillé et triangle creux).
C'est le partage des rôles typique : la classe abstraite regroupe ce que les matériels sont (des données communes et un calcul commun dans son principe), l'interface une capacité que seuls certains ont. Une classe ne peut avoir qu'une mère, mais réaliser plusieurs interfaces.
Tu avais trouvé ?
Exercice 3 sur 4 La classe-association Réussi À revoir
Dans une école, un étudiant obtient une note dans chaque matière qu'il suit, à la fin du semestre. Un étudiant suit plusieurs matières ; une matière est suivie par plusieurs étudiants.
- Où mettre l'attribut
note? Dessine la solution. - L'école veut désormais garder toutes les notes de l'étudiant dans une matière (plusieurs contrôles par semestre, chacun à une date). La solution tient-elle toujours ?
Voir la réponseMasquer la réponse de l'exercice 3
- La note n'appartient ni à l'étudiant (il en a une par matière) ni à la matière (elle en a une par étudiant) : c'est une classe-association
Inscriptionavec- note : decimal [0..1](pas de note avant la fin du semestre), reliée par un pointillé à l'associationEtudiant "0..*" ──── "0..*" Matiere. - Non : une classe-association n'autorise qu'un lien par couple étudiant-matière, donc une seule note. On la remplace par une classe ordinaire
Evaluation(- date : DateOnly,- note : decimal), reliée par deux associations :Etudiant "1" ──── "0..*" Evaluation "0..*" ──── "1" Matiere. Chaque évaluation concerne un étudiant et une matière, et un même couple peut en avoir plusieurs.
Tu avais trouvé ?
Exercice 4 sur 4 Faire évoluer le diagramme d'AppliFrais Réussi À revoir
GSB veut rembourser les kilomètres selon la puissance du véhicule : chaque visiteur déclare au plus un véhicule personnel (immatriculation, puissance fiscale en chevaux) ; le barème associe à chaque tranche de puissance un tarif au kilomètre (par exemple 0,62 € jusqu'à 4 CV, 0,67 € pour 5 CV, 0,70 € au-delà). Plusieurs visiteurs peuvent avoir des véhicules de même puissance. Complète le diagramme de classes : nouvelles classes, attributs, associations et multiplicités.
Voir la réponseMasquer la réponse de l'exercice 4
- Classe
Vehicule:- immatriculation : string,- puissanceCv : int. - Classe
TrancheBareme:- puissanceMin : int,- puissanceMax : int [0..1](vide pour la dernière tranche, « au-delà »),- tarifKm : decimal. Visiteur "1" ──── "0..1" Vehicule: un visiteur a zéro ou un véhicule ; un véhicule appartient à un seul visiteur. Ce n'est pas une composition au sens strict (le véhicule existe sans l'application), une association suffit.Vehicule "0..*" ──── "1" TrancheBareme: un véhicule relève d'une tranche ; une tranche concerne zéro ou plusieurs véhicules. On peut aussi ne pas tracer cette association et la calculer à partir de la puissance : la tranche se déduit depuissanceCv. Dans ce cas, on ajoute une opération+ TarifKm() : decimaldansVehicule, qui consulte le barème, et une note qui explique la règle.
Le forfait « KM » de FraisForfait ne suffit plus à calculer le montant : LigneFraisForfait.Montant() doit utiliser le tarif du véhicule du visiteur pour la ligne KM. Signaler cette conséquence sur le code est exactement ce qu'on attend dans une question d'évolution.
Tu avais trouvé ?