cursus.

Cours 4 · Robustesse et modélisationLeçon 2 sur 2

Modélisation UML

5 h de lecture9 sections Version PDF

À la fin de cette leçon, vous saurez

Diagramme de classes : attributs, méthodes, visibilité, associations et multiplicités, héritage et implémentation, agrégation et composition ; du diagramme au code, et retour.

Sept chapitres ont construit un vocabulaire : classe, attribut, visibilité, héritage, interface, composition. Il manque un moyen de montrer tout cela sans faire lire le code.

C'est ce que fait un diagramme de classes. Un programme de dix classes se lit en une minute sur un schéma, contre une heure dans les fichiers — et surtout, il se discute : c'est l'artefact qu'on met au tableau quand trois personnes doivent se mettre d'accord avant d'écrire.

UML compte quatorze types de diagrammes. Ce chapitre n'en traite qu'un, celui de classes, parce qu'il est le seul qu'on emploie vraiment en L2 — et parce qu'il est en correspondance directe avec le code.

La classe

┌─────────────────────────────┐│          Livre              │   ← nom (en italique si abstraite)├─────────────────────────────┤│ - titre : String            │   ← attributs│ - disponible : boolean      ││ + NB_MAX : int              │   ← souligné si static├─────────────────────────────┤│ + emprunter(qui : String)   │   ← méthodes│   : boolean                 ││ + estDisponible() : boolean ││ - verifier() : void         │└─────────────────────────────┘

Trois compartiments : le nom, les attributs, les méthodes. Les deux derniers peuvent être omis quand ils n'apportent rien au propos — un diagramme n'est pas une transcription.

Les symboles de visibilité reprennent exactement le chapitre 3 :

SymboleVisibilité
-private
+public
#protected
~paquetage (par défaut)

Deux conventions typographiques valent d'être connues, car elles se lisent d'un coup d'œil : souligné signifie static, italique signifie abstrait — pour une classe comme pour une méthode.

Les relations

C'est le cœur du chapitre, et chaque trait a un sens précis.

Livre ──────────▷ Document          héritage       (triangle creux, trait plein)Livre ┄┄┄┄┄┄┄┄┄▷ Empruntable        réalisation    (triangle creux, pointillés)Commande ◇────── Article            agrégation     (losange creux)Maison  ◆────── Piece               composition    (losange plein)Etudiant ─────── Cours              association    (trait simple)Facture ┄┄┄┄┄┄> Calculatrice        dépendance     (flèche pointillée)

L'héritage — triangle creux, trait plein, pointé vers le parent — traduit extends. C'est le « est-un » du chapitre 5, avec le même critère de substituabilité.

La réalisation — même triangle, mais en pointillés — traduit implements. La distinction visuelle est délibérée : hériter et implémenter ne sont pas la même chose.

L'association est un simple trait : deux classes se connaissent. En pratique, cela devient un attribut d'un côté ou des deux.

L'agrégation et la composition sont deux associations particulières, et leur différence est la seule subtilité du chapitre. Toutes deux expriment « a-un » ; ce qui les sépare est la durée de vie.

Dans une agrégation (losange creux), la partie survit au tout. Une Commande agrège des Article : supprimer la commande ne supprime pas les articles, qui existaient avant et continueront après.

Dans une composition (losange plein), la partie meurt avec le tout, et n'appartient qu'à lui. Une Maison compose ses Piece : détruire la maison détruit les pièces, et une pièce n'appartient pas à deux maisons.

Le test pratique : « si je supprime le tout, la partie a-t-elle encore un sens ? » Oui, agrégation ; non, composition.

Enfin la dépendance — flèche pointillée — signale un usage passager : une classe reçoit l'autre en paramètre ou l'emploie localement, sans la stocker.

Multiplicités

Un chiffre à chaque extrémité dit combien d'objets participent.

Bibliotheque  1 ◆────── 0..*  LivreLivre         *  ─────── 0..1  Lecteur
NotationSens
1exactement un
0..1zéro ou un — donc peut être null
* ou 0..*zéro ou plusieurs
1..*au moins un
2..4entre deux et quatre

Les multiplicités sont la partie la plus utile du diagramme, et la plus souvent oubliée. Elles répondent à des questions que le code ne pose pas explicitement : un livre peut-il être emprunté par deux lecteurs à la fois ? un lecteur sans emprunt est-il légal ? Un 0..1 du côté Lecteur répond « non » et « oui », et impose une décision qu'on aurait sinon prise par accident.

Voici ces relations réunies dans un diagramme interactif. Chaque trait porte son symbole UML — triangle d'héritage, losange de composition ou d'agrégation — et ses multiplicités ; déplacez les classes pour démêler les liens, et retrouvez la composition Bibliotheque ◆ Livre, l'héritage Livre ▷ Document et la réalisation de l'interface Empruntable.

Diagramme · Un diagramme de classes et ses relations

  • Document — attributs : - titre : String
  • Empruntable — méthodes : + emprunter() : boolean
  • Livre — attributs : - disponible : boolean — méthodes : + emprunter() : boolean
  • Bibliotheque — attributs : - livres : List<Livre>
  • Lecteur — attributs : - nom : String
  • Commande
  • Article — attributs : - prix : double

Relations

  • Livre ▷ hérite Document
  • Livre ▷ réalise Empruntable
  • Bibliotheque 1 ◆ compose 0..* Livre
  • Livre emprunté par 0..1 Lecteur
  • Commande ◇ agrège 0..* Article

Du diagramme au code

La traduction est presque mécanique, et c'est ce qui rend le diagramme utile plutôt que décoratif.

Sur le diagrammeDans le code Java
- titre : Stringprivate String titre;
Attribut soulignéstatic
Classe en italiqueabstract class
Triangle creux pleinextends
Triangle creux pointilléimplements
Association 1un attribut du type visé
Association 0..1un attribut, qui peut être null
Association *List<Type> ou Map<Clé, Type>
Compositionle tout crée ses parties dans son constructeur
Agrégationles parties sont reçues en paramètre

Les deux dernières lignes méritent qu'on s'y arrête, car elles transforment une nuance sémantique en différence de code visible :

class Maison {                          /* COMPOSITION */    private final List<Piece> pieces = new ArrayList<>();    public Maison(int nb) {        for (int i = 0; i < nb; i++) pieces.add(new Piece());   /* elle les crée */    }} class Commande {                        /* AGRÉGATION */    private final List<Article> articles;    public Commande(List<Article> articles) {        this.articles = articles;       /* elle les reçoit */    }}

Le chemin inverse compte autant : savoir lire du code existant et en tirer un diagramme est ce qu'on fait le premier jour sur un projet qu'on n'a pas écrit. C'est aussi ce que font les outils de rétro-ingénierie des ateliers de développement.

Ce qu'un diagramme ne dit pas, et ce qu'il ne faut pas en faire

Trois limites, pour ne pas prendre le dessin pour le programme.

Il ne dit rien du comportement. L'ordre des appels, les conditions, les boucles n'y figurent pas — c'est le rôle d'autres diagrammes UML, hors programme, comme celui de séquence.

Il vieillit. Un diagramme qui n'est pas maintenu devient un mensonge, plus nuisible qu'une absence de documentation. D'où deux usages viables : le dessin jetable au tableau avant de coder, et le diagramme régénéré depuis le code.

Il ne doit pas tout montrer. Un diagramme de quarante classes avec tous les getters est illisible et n'aide personne. On dessine ce qui répond à la question du moment : les relations principales, sans les accesseurs, sans les classes utilitaires.

Le modéliser-avant-tout des années 1990 a d'ailleurs largement été abandonné. Ce qui reste, et qui est ce qu'on enseigne ici : un croquis de cinq à dix classes, fait avant d'écrire, pour se mettre d'accord.

Quiz · vérifiez votre compréhension Sans réponse

Quelle est la différence entre agrégation et composition, et comment la trancher ?

Quiz · vérifiez votre compréhension Sans réponse

Sur un diagramme, la relation Livre * ── 0..1 Lecteur. Que dit-elle, et comment se traduit-elle ?

À vous

L'exercice fait les deux trajets, et c'est le second qui est formateur.

Du diagramme au code : on vous donne un diagramme décrit sous forme de données — classes, attributs, relations, multiplicités — et vous écrivez le générateur qui en produit les squelettes Java. Il devra traduire correctement 1, 0..1 et *, et distinguer agrégation et composition par la façon dont les parties arrivent.

Du code au diagramme : on vous donne des classes, et vous en extrayez les relations. Un cas est ambigu — un attribut de type List peut être une agrégation ou une composition — et c'est en regardant qui crée les objets que vous trancherez.

Un dernier contrôle vérifie les multiplicités sur des données : un livre à deux emprunteurs simultanés doit être signalé comme violant le diagramme.

Exercice · JavaScript · à vous de jouer

Générez du Java depuis un diagramme, puis retrouvez les relations à partir du code — agrégation ou composition ?

En attente
// ── Un diagramme, décrit sous forme de données ────────────────────────────
const DIAGRAMME = {
  classes: [
    { nom: "Document", abstraite: true,
      attributs: [{ v: "-", nom: "titre", type: "String" }],
      methodes:  [{ v: "+", nom: "decrire", type: "String", abstraite: true }] },
    { nom: "Livre", herite: "Document", implemente: ["Empruntable"],
      attributs: [{ v: "-", nom: "isbn", type: "long" },
                  { v: "+", nom: "NB_MAX", type: "int", statique: true }],
      methodes:  [{ v: "+", nom: "decrire", type: "String" }] },
    { nom: "Bibliotheque",
      attributs: [{ v: "-", nom: "nom", type: "String" }],
      methodes:  [] },
    { nom: "Lecteur", attributs: [{ v: "-", nom: "nom", type: "String" }], methodes: [] },
  ],
  relations: [
    { de: "Bibliotheque", vers: "Livre", type: "composition", multiplicite: "*" },
    { de: "Lecteur", vers: "Livre", type: "agregation", multiplicite: "*" },
    { de: "Livre", vers: "Lecteur", type: "association", multiplicite: "0..1" },
  ],
};

// ── 1. Du diagramme au code ───────────────────────────────────────────────
function versJava(d) {
  const lignes = [];
  for (const c of d.classes) {
    let entete = (c.abstraite ? "abstract " : "") + "class " + c.nom;
    if (c.herite) entete += " extends " + c.herite;
    if (c.implemente) entete += " implements " + c.implemente.join(", ");
    lignes.push("public " + entete + " {");

    const VISIBILITE = { "-": "private", "+": "public", "#": "protected", "~": "" };
    for (const a of c.attributs) {
      lignes.push("    " + [VISIBILITE[a.v], a.statique ? "static final" : "", a.type, a.nom]
        .filter(Boolean).join(" ") + ";");
    }
    // ← à écrire : traduire les relations dont cette classe est l'origine.
    //   « 1 » et « 0..1 » donnent un attribut simple, « * » donne une List.
    lignes.push("}");
    lignes.push("");
  }
  return lignes.join("\n");
}

// ── 2. Du code au diagramme ───────────────────────────────────────────────
const SOURCES = {
  Panier: [
    "class Panier {",
    "  private List<Article> articles;",
    "  Panier(List<Article> articles) { this.articles = articles; }",   // reçus
    "}",
  ],
  Maison: [
    "class Maison {",
    "  private List<Piece> pieces = new ArrayList<>();",
    "  Maison(int n) { for (int i=0;i<n;i++) pieces.add(new Piece()); }", // créées
    "}",
  ],
};

function versDiagramme(nom, source) {
  const texte = source.join("\n");
  const champ = /private List<(\w+)>/.exec(texte);
  if (!champ) return { de: nom, type: "aucune relation détectée" };
  // ← à écrire : agrégation ou composition ? Regardez QUI CRÉE les objets.
  return { de: nom, vers: champ[1], type: "à déterminer", multiplicite: "*" };
}

// ── À VOUS ────────────────────────────────────────────────────────────────
// 1. Complétez versJava pour les trois multiplicités.
// 2. Complétez versDiagramme : le critère est « qui crée les objets ».
// 3. Écrivez un contrôle de multiplicité : un livre emprunté par deux
//    lecteurs simultanément viole le 0..1 du diagramme.

console.log(versJava(DIAGRAMME));

Console de sortie
Le résultat s'affiche dans la console

En travaux pratiques

Travaux pratiques 8 · sur machine

Le diagramme comme outil de décision

Dessiner AVANT de coder, puis rétro-modéliser le code écrit, et mesurer l'écart entre les deux — c'est l'écart qui est instructif, pas le diagramme.

2 h
Avant de commencer
  • Les TP 1 à 7 : la médiathèque en état de marche
  • Un outil de diagramme, ou du papier
  1. 1. Rétro-modéliser

    Dessinez le diagramme de classes de votre médiathèque telle qu'elle est aujourd'hui. Classes, attributs, méthodes publiques, et les relations entre elles.

  2. 2. Nommer les relations

    Pour chaque trait de votre diagramme, dites s'il s'agit d'un héritage, d'une composition, d'une agrégation ou d'une simple association, et justifiez.

  3. 3. Les cardinalités

    Annotez chaque association de ses cardinalités. Vérifiez ensuite dans le code que chacune est réellement appliquée.

  4. 4. Ce que le diagramme révèle

    Repérez sur votre dessin : une classe qui connaît trop de monde, une relation dans les deux sens, un cycle de dépendances. Corrigez-en un dans le code.

  5. 5. Concevoir en amont

    Nouvelle exigence : gérer les adhérents et les emprunts avec dates. Dessinez le modèle AVANT d'écrire une ligne. Discutez-en à deux, dessin contre dessin.

  6. 6. Le diagramme de séquence

    Dessinez le déroulement d'un emprunt : qui appelle qui, dans quel ordre, et où la règle métier est vérifiée. Trouvez si la vérification est au bon endroit.

  7. 7. Coder puis comparer

    Implémentez ce que vous avez dessiné, puis rétro-modélisez à nouveau. Listez les écarts et dites, pour chacun, qui avait raison — le dessin ou le code.

C'est réussi quand
  • Vous distinguez composition et agrégation sur un exemple de votre propre code
  • Vos cardinalités sont réellement appliquées, ou bien vous savez dire où elles ne le sont pas
  • Vous listez au moins deux écarts entre le dessin initial et le code final

Ce que la suite en fait

Le bloc V met tout en œuvre. Le chapitre 9 donne les collections qui matérialisent les multiplicités *List, Map, Set — et l'on verra que le choix entre elles est lui-même une décision de modélisation : un Set interdit les doublons, une Map impose une clé unique.

Le chapitre 10 partira d'un énoncé en français pour arriver à un diagramme, puis au code. La notation de ce chapitre y sera l'outil de travail, et non plus le sujet.

À retenir

Flashcards · 1 / 5Toucher pour retourner
Fin de la leçon

Vous avez parcouru les 9 sections.

Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.