Modélisation UMLDans le dialogue d’impression, choisissez « Enregistrer au format PDF » comme destination.
Retour

Programmation orientée objet · C4 Robustesse et modélisation · Chapitre 2 · 5 h

Modélisation UML

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 · 1 question

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

  • L'agrégation concerne un seul objet, la composition plusieursquestion de nombre
  • La DURÉE DE VIE : dans une composition la partie meurt avec le tout et n'appartient qu'à lui ; dans une agrégation elle lui survit. Test : « si je supprime le tout, la partie a-t-elle encore un sens ? »question de durée de vie
  • L'agrégation se traduit par un attribut, la composition par un héritagequestion de traduction

Réponse : Les deux expriment « a-un » et se traduisent toutes deux par un attribut — c'est bien pourquoi on les confond. Le critère est la DURÉE DE VIE et l'exclusivité. COMPOSITION (losange plein) : une Maison et ses Pièces — détruire la maison détruit les pièces, et une pièce n'appartient pas à deux maisons. AGRÉGATION (losange creux) : une Commande et ses Articles — supprimer la commande ne supprime pas les articles, qui existaient avant et continueront après. La différence se voit d'ailleurs dans le code : en composition, le tout CRÉE ses parties dans son constructeur ; en agrégation, il les REÇOIT en paramètre. Ni l'une ni l'autre n'a de rapport avec l'héritage, qui exprime « est-un ».

Quiz · 1 question

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

  • Un livre a exactement un lecteur, et un lecteur a plusieurs livres : deux attributs obligatoiresun lecteur obligatoire
  • Un livre est emprunté par ZÉRO OU UN lecteur — donc un attribut Lecteur pouvant être null, ce qui interdit deux emprunteurs simultanés — et un lecteur peut avoir plusieurs livres, donc une List du côté Lecteurzéro ou un, et une liste
  • La multiplicité est décorative : elle ne change rien au code produitdécorative

Réponse : Les multiplicités se lisent en croisé : celle placée près de Lecteur dit combien de lecteurs un livre voit, et réciproquement. Ici 0..1 côté Lecteur signifie qu'un livre est emprunté par au plus un lecteur — donc un attribut Lecteur qui peut valoir null, et l'impossibilité structurelle de deux emprunteurs simultanés. Le * côté Livre signifie qu'un lecteur peut détenir plusieurs livres, ce qui donne une List côté Lecteur. Loin d'être décoratives, les multiplicités répondent à des questions que le code ne pose pas explicitement — un livre peut-il avoir deux emprunteurs ? un lecteur sans emprunt est-il légal ? — et forcent une décision qu'on aurait sinon prise par accident. C'est la partie la plus utile du diagramme, et la plus souvent oubliée.

À 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 de code

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

Point de départ

// ── 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));

Solution

const DIAGRAMME = {
  classes: [
    { nom: "Document", abstraite: true,
      attributs: [{ v: "-", nom: "titre", type: "String" }], methodes: [] },
    { nom: "Livre", herite: "Document", implemente: ["Empruntable"],
      attributs: [{ v: "-", nom: "isbn", type: "long" },
                  { v: "+", nom: "NB_MAX", type: "int", statique: true }], methodes: [] },
    { 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" },
  ],
};

const VISIBILITE = { "-": "private", "+": "public", "#": "protected", "~": "" };

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 + " {");

    for (const a of c.attributs) {
      lignes.push("    " + [VISIBILITE[a.v], a.statique ? "static final" : "", a.type, a.nom]
        .filter(Boolean).join(" ") + ";");
    }

    const sortantes = d.relations.filter((r) => r.de === c.nom);
    for (const r of sortantes) {
      const pluriel = r.vers.toLowerCase() + "s";
      if (r.multiplicite === "*" || r.multiplicite === "1..*") {
        // Une multiplicité multiple devient une collection. En COMPOSITION,
        // le tout crée ses parties : la liste est initialisée sur place et
        // final. En AGRÉGATION, elle est reçue par le constructeur.
        const init = r.type === "composition" ? " = new ArrayList<>()" : "";
        const finalite = r.type === "composition" ? "final " : "";
        lignes.push("    private " + finalite + "List<" + r.vers + "> " + pluriel + init + ";");
      } else if (r.multiplicite === "0..1") {
        lignes.push("    private " + r.vers + " " + r.vers.toLowerCase() +
                    ";   // peut être null");
      } else {
        lignes.push("    private final " + r.vers + " " + r.vers.toLowerCase() + ";");
      }
    }
    lignes.push("}");
    lignes.push("");
  }
  return lignes.join("\n");
}

console.log("— 1. du diagramme au code —");
console.log(versJava(DIAGRAMME));

console.log("— 2. du code au diagramme —");
const SOURCES = {
  Panier: ["class Panier {", "  private List<Article> articles;",
           "  Panier(List<Article> articles) { this.articles = articles; }", "}"],
  Maison: ["class Maison {", "  private List<Piece> pieces = new ArrayList<>();",
           "  Maison(int n) { for (int i=0;i<n;i++) pieces.add(new Piece()); }", "}"],
};

function versDiagramme(nom, source) {
  const texte = source.join("\n");
  const champ = /private (?:final )?List<(\w+)>/.exec(texte);
  if (!champ) return { de: nom, type: "aucune relation détectée" };
  const partie = champ[1];
  // LE critère : qui crée les objets ? Un « new Partie() » à l'intérieur
  // signale une composition ; une réception en paramètre, une agrégation.
  const cree = new RegExp("new " + partie + "\\s*\\(").test(texte);
  const recu = new RegExp("\\(\\s*List<" + partie + ">").test(texte);
  const type = cree ? "composition" : recu ? "agrégation" : "indéterminé";
  return { de: nom, vers: partie, type, multiplicite: "*",
           indice: cree ? "crée ses parties" : "les reçoit en paramètre" };
}

for (const [nom, src] of Object.entries(SOURCES)) {
  const r = versDiagramme(nom, src);
  console.log("   " + r.de.padEnd(10) + (r.type === "composition" ? "◆" : "◇") +
              "──── " + r.vers.padEnd(9) + r.multiplicite.padEnd(4) +
              r.type.padEnd(13) + "(" + r.indice + ")");
}
console.log("   Le même attribut List<X> donne deux relations différentes : la");
console.log("   nuance n'est pas dans le type, elle est dans QUI CRÉE.");

console.log("");
console.log("— 3. contrôle des multiplicités —");
// Le diagramme dit : Livre 0..1 Lecteur. Deux emprunteurs sont donc interdits.
const emprunts = [
  { livre: "Dune", lecteur: "Ana" },
  { livre: "Ubik", lecteur: "Bo" },
  { livre: "Dune", lecteur: "Cy" },     // viole le 0..1
];
const parLivre = new Map();
for (const e of emprunts) {
  parLivre.set(e.livre, [...(parLivre.get(e.livre) ?? []), e.lecteur]);
}
for (const [livre, lecteurs] of parLivre) {
  const ok = lecteurs.length <= 1;
  console.log("   " + livre.padEnd(6) + lecteurs.length + " lecteur(s) : " +
    lecteurs.join(", ") + (ok ? "   conforme au 0..1" : "   VIOLE le 0..1 du diagramme"));
}
console.log("   Sans la multiplicité, ce cas serait passé inaperçu jusqu'à ce");
console.log("   qu'un utilisateur s'en plaigne.");

En travaux pratiques

Travaux pratiques 8 · 2 h

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.

Avant de commencer

  • Les TP 1 à 7 : la médiathèque en état de marche
  • Un outil de diagramme, ou du papier

Énoncé

  1. Rétro-modéliserDessinez 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. Nommer les relationsPour 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. Indice : La question qui tranche entre composition et agrégation : si le tout disparaît, la partie disparaît-elle aussi ?
  3. Les cardinalitésAnnotez chaque association de ses cardinalités. Vérifiez ensuite dans le code que chacune est réellement appliquée.
  4. Ce que le diagramme révèleRepé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. Concevoir en amontNouvelle 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. Le diagramme de séquenceDessinez 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. Coder puis comparerImplé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

Correction

Le diagramme de la médiathèque
┌──────────────────┐          ┌──────────────────┐
│   Mediatheque    │◆────────▶│    Document      │
├──────────────────┤ 1    0..*├──────────────────┤
│ -documents: List │          │ #titre: String   │
├──────────────────┤          │ #disponible: bool│
│ +ajouter()       │          ├──────────────────┤
│ +chercher()      │          │ +emprunter()     │
│ +penalite()      │          │ +penaliteParJour()│ {abstract}
└──────────────────┘          └────────△─────────┘
                                     │
                      ┌──────────────┼──────────────┐
                   ┌──┴───┐      ┌───┴──┐      ┌────┴───┐
                   │Livre │      │ Dvd  │      │ Revue  │
                   └──────┘      └──────┘      └────────┘

◆── composition   △── héritage   - privé  # protégé  + public

Le diagramme rend visible en un coup d'œil ce qu'il faut lire cinq fichiers pour comprendre : une hiérarchie à trois feuilles et une seule relation de possession. C'est là son utilité — pas remplacer le code, mais montrer sa forme.

Composition ou agrégation
COMPOSITION (losange PLEIN) : le tout POSSÈDE la partie
si le tout disparaît, la partie disparaît
Mediatheque ◆── Document ? discutable : un document peut être
transféré ailleurs → plutôt une AGRÉGATION

AGRÉGATION (losange VIDE) : le tout RÉFÉRENCE la partie
Emprunt ◇── Adherent : l'adhérent survit à l'emprunt

la vraie composition, ici :
Emprunt ◆── DateEmprunt : la date n'existe que par l'emprunt

La question qui tranche est celle du cycle de vie, pas celle du sentiment de possession. Dans le code, la composition se traduit par un objet créé et détruit par son propriétaire, et jamais partagé ; l'agrégation, par une référence reçue de l'extérieur. C'est aussi la question qui décide s'il faut une copie défensive — celle du TP 3.

Le modèle des emprunts
Adherent 1 ──── 0..* Emprunt 0..* ──── 1 Document

class Emprunt {
  private final Adherent adherent;      /* agrégation */
  private final Document document;      /* agrégation */
  private final LocalDate debut;
  private LocalDate retour;             /* null tant que non rendu */
}

/* l'erreur fréquente : mettre l'emprunteur DANS Document
 → un document ne peut alors avoir qu'un emprunt, jamais d'historique */

Une association porteuse d'informations — les dates — devient une CLASSE. C'est l'erreur de conception la plus fréquente à ce stade : loger la relation dans l'un des deux objets, ce qui interdit l'historique et casse dès qu'un second emprunt survient. Le diagramme la fait voir immédiatement, le code ne la révèle qu'à l'usage.

Le diagramme de séquence, et ce qu'il déplace
Interface   Mediatheque   Document   Adherent
  │            │             │          │
  │─emprunter─▶│             │          │
  │            │──peutEmprunter()───────▶│   quota atteint ?
  │            │◀────true────────────────│
  │            │──emprunter()▶│              disponible ?
  │            │◀───true──────│
  │            │─── new Emprunt() ───▶
  │◀───ok──────│

la règle « un adhérent ne dépasse pas 5 emprunts » est chez
l'ADHÉRENT, pas dans Mediatheque : c'est lui qui la connaît

Le diagramme de séquence répond à une question que le diagramme de classes ne pose pas : qui DÉCIDE. Ici, il fait apparaître que la règle des cinq emprunts était écrite dans Mediatheque alors qu'elle dépend de l'état de l'adhérent — le « tell, don't ask » du TP 3, rendu visible par un dessin.

L'écart entre le dessin et le code
écarts typiques après implémentation :
- une classe utilitaire non prévue (validation, formatage)
- une interface extraite pour pouvoir tester
- une relation bidirectionnelle prévue, rendue unidirectionnelle
  (la bidirectionnelle est pénible à maintenir cohérente)
- une méthode prévue, jamais appelée → supprimée

le CODE a souvent raison sur le détail,
le DESSIN a souvent raison sur la structure

Le diagramme n'est pas un contrat à faire respecter : c'est un outil de discussion et de repérage. Un diagramme entretenu à l'identique du code ne sert à rien — la lecture du code y suffirait. Ce qui vaut, c'est le diagramme de conception, fait avant, sur un tableau, à deux, et jeté ensuite ; et la rétro-modélisation, faite après, pour voir la forme de ce qu'on a réellement construit.

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 · 5 cartes

Comment se lisent les symboles d'une classe UML ?
Trois compartiments : nom, attributs, méthodes — les deux derniers pouvant être omis, un diagramme n'étant pas une transcription. Visibilité : − private, + public, # protected, ~ paquetage. Deux conventions typographiques qui se lisent d'un coup d'œil : SOULIGNÉ signifie static, ITALIQUE signifie abstrait, pour une classe comme pour une méthode.
Quels traits pour l'héritage, l'implémentation, l'association et la dépendance ?
HÉRITAGE : triangle creux, trait PLEIN, pointé vers le parent — traduit extends. RÉALISATION : même triangle en POINTILLÉS — traduit implements ; la distinction visuelle est délibérée. ASSOCIATION : trait simple, deux classes se connaissent, ce qui devient un attribut d'un côté ou des deux. DÉPENDANCE : flèche pointillée, usage passager — la classe reçoit l'autre en paramètre ou l'emploie localement, sans la stocker.
Agrégation ou composition : le critère et sa trace dans le code ?
La DURÉE DE VIE et l'exclusivité. COMPOSITION (losange plein) : la partie meurt avec le tout et n'appartient qu'à lui — une Maison et ses Pièces. AGRÉGATION (losange creux) : la partie survit au tout — une Commande et ses Articles. Test : « si je supprime le tout, la partie a-t-elle encore un sens ? ». Dans le code : en composition le tout CRÉE ses parties dans son constructeur, en agrégation il les REÇOIT en paramètre.
Comment traduit-on les multiplicités en code, et pourquoi comptent-elles ?
1 → un attribut du type visé ; 0..1 → un attribut pouvant être null ; * ou 0..* → une List ou une Map ; 1..* → une liste jamais vide, invariant à maintenir. Elles comptent parce qu'elles répondent à des questions que le code ne pose pas explicitement — un livre peut-il avoir deux emprunteurs ? un lecteur sans emprunt est-il légal ? — et forcent une décision qu'on prendrait sinon par accident. C'est la partie la plus utile du diagramme, et la plus souvent oubliée.
Quelles sont les trois limites d'un diagramme de classes ?
1) IL NE DIT RIEN DU COMPORTEMENT : ordre des appels, conditions, boucles n'y figurent pas. 2) IL VIEILLIT : non maintenu, il devient un mensonge plus nuisible qu'une absence de documentation — d'où deux usages viables, le croquis JETABLE avant de coder et le diagramme RÉGÉNÉRÉ depuis le code. 3) IL NE DOIT PAS TOUT MONTRER : quarante classes avec tous les getters n'aident personne. Ce qui reste du modéliser-avant-tout des années 1990 : un croquis de cinq à dix classes, fait avant d'écrire, pour se mettre d'accord.