Analyser un énoncé, en tirer un diagramme de classes, implémenter par incréments, tester, et restituer.
L'énoncé tient en trois lignes :
Réaliser une application de gestion de bibliothèque. Les adhérents empruntent des ouvrages pour une durée limitée. Un ouvrage peut exister en plusieurs exemplaires. Les retards donnent lieu à une pénalité.
Le réflexe est d'ouvrir l'éditeur et d'écrire public class Main. C'est la façon la plus sûre
de produire, trois semaines plus tard, une classe de huit cents lignes que personne ne peut
modifier.
Ce dernier chapitre n'introduit aucune notion. Il dit ce qu'on fait avant d'écrire la première ligne, et dans quel ordre — c'est le seul moment du semestre où l'on conçoit au lieu d'appliquer.
Analyser l'énoncé
La méthode est vieille, un peu naïve, et elle fonctionne : souligner les noms, entourer les verbes.
Les noms communs sont des candidats-classes : adhérent, ouvrage, exemplaire, emprunt, pénalité, bibliothèque. Les verbes sont des candidats-méthodes : emprunter, rendre, calculer une pénalité, rechercher.
Vient ensuite le filtrage, et c'est là que le travail commence — un nom n'est pas toujours une classe.
« Durée » n'est pas une classe : c'est un attribut d'Emprunt, ou une constante de la
bibliothèque. Un nom qui n'a ni comportement ni identité propre est une donnée.
« Pénalité » est douteux. Si c'est un simple montant, c'est une valeur calculée. Si elle a une date, un statut payé ou non, un historique — alors elle a une identité, et c'est une classe. L'énoncé ne tranche pas : c'est une question à poser, et savoir qu'il faut la poser vaut mieux que deviner.
« Ouvrage » et « exemplaire » sont deux classes distinctes, et c'est la vraie difficulté de cet énoncé. « Un ouvrage peut exister en plusieurs exemplaires » dit exactement cela : Dune est un ouvrage — un titre, un auteur, un ISBN — dont la bibliothèque possède trois exemplaires physiques, chacun avec sa cote et son état. On n'emprunte pas un ouvrage, on emprunte un exemplaire.
Les fusionner est l'erreur numéro un sur cet énoncé, et elle se paie immédiatement : on ne sait plus dire combien d'exemplaires sont disponibles.
Le filtre final est celui du chapitre 1 : chaque classe retenue doit se décrire en une phrase sans « et ». Si l'on n'y arrive pas, il y en a deux.
Concevoir le diagramme
On dessine ensuite, avec la notation du chapitre 8 — et l'on vise cinq à huit classes. Au-delà sur un projet de L2, c'est généralement qu'on a modélisé des détails.
Adherent 1 ───── 0..* Emprunt 0..* ───── 1 Exemplaire * ───── 1 OuvrageTrois décisions se lisent dans cette seule ligne, et chacune était une question ouverte.
Emprunt est devenu une classe à part entière, et non une simple association. La raison est
qu'il porte des données propres — date de début, date de retour prévue, date de retour
effective — et un comportement : calculer son retard. Une association qui porte des attributs
est une classe.
La multiplicité 0..* du côté Emprunt dit qu'un adhérent peut n'avoir aucun emprunt en cours,
et plusieurs à la fois. Un 0..3 dirait « trois au maximum », ce qui est une règle métier
qu'il faut alors faire respecter quelque part.
Et Exemplaire → Ouvrage en * ── 1 traduit exactement la phrase de l'énoncé.
Une fois le diagramme posé, on le vérifie en y faisant marcher un scénario : « Ana emprunte Dune, le rend avec cinq jours de retard, paie sa pénalité ». On suit le chemin sur le dessin. Si une information manque — comment retrouver un exemplaire libre de cet ouvrage ? — le diagramme est incomplet, et il vaut mieux s'en apercevoir maintenant.
Implémenter par incréments
C'est ici que la plupart des projets de L2 se perdent, et l'erreur est toujours la même : écrire toutes les classes, puis tous les attributs, puis toutes les méthodes, et essayer de faire tourner l'ensemble la veille du rendu. Rien ne compile jamais avant la fin, et le premier test a lieu quand il est trop tard.
La méthode qui marche est la tranche verticale : choisir un scénario complet, le faire fonctionner de bout en bout, puis passer au suivant.
Incrément 1 créer un ouvrage, un exemplaire, les afficherIncrément 2 un adhérent emprunte un exemplaire disponibleIncrément 3 le refus si l'exemplaire est déjà emprunté (exceptions)Incrément 4 le retour, et le calcul du retardIncrément 5 la pénalité, et l'historiqueIncrément 6 la recherche par titre et par auteur (collections)Trois bénéfices, et le troisième est le plus important.
Le programme compile et s'exécute en permanence. Une régression se détecte le jour même, pas trois semaines plus tard.
On a toujours quelque chose à montrer. Un projet à moitié fait mais qui tourne vaut mieux qu'un projet complet qui ne compile pas — c'est vrai pour la note comme pour la suite.
La conception se corrige en marchant. L'incrément 3 révélera peut-être qu'il faut une classe
Reservation à laquelle on n'avait pas pensé. La découvrir en écrivant coûte une heure ; la
découvrir à la fin coûte une refonte.
Tester
Un jeu de tests, même minimal, n'est pas un supplément : c'est ce qui permet de modifier sans casser.
Quatre choses méritent d'être testées dans un programme objet, et elles ne sont pas les mêmes qu'en programmation impérative.
Les invariants du chapitre 3 : un constructeur refuse-t-il bien un ISBN vide, un solde négatif, une date de retour antérieure à la date d'emprunt ?
Les cas limites, comme au chapitre 10 du cours de programmation : emprunter le dernier exemplaire disponible, rendre un jour pile après l'échéance, une liste vide.
Le comportement polymorphe : une méthode redéfinie fait-elle bien ce qu'elle doit dans chaque sous-classe ? C'est le seul endroit où le chapitre 6 peut se vérifier mécaniquement.
Les exceptions : la bonne exception est-elle levée dans le bon cas ? Un test qui vérifie qu'un emprunt impossible échoue vaut autant qu'un test de succès.
Un dernier bénéfice, moins évident : écrire un test, c'est être le premier client de sa propre API. Si le test est pénible à écrire — s'il faut créer sept objets pour en tester un — la conception est trop couplée. Le test révèle le défaut avant l'utilisateur.
Restituer
Le rendu compte, et il tient en peu de chose.
Un fichier de description — ce que fait le programme, comment le compiler et le lancer, ce qui est fait et ce qui ne l'est pas. La dernière partie est celle qu'on omet, et c'est la plus appréciée : un projet honnête sur ses limites est jugé plus favorablement qu'un projet qui les cache.
Le diagramme de classes, tel qu'il est à la fin — pas celui du début. S'ils diffèrent, c'est normal, et l'écart vaut d'être expliqué en deux phrases : c'est la preuve qu'on a conçu en marchant.
Un jeu de données de démonstration, pour que le correcteur n'ait pas à en inventer.
Les cinq pièges classiques
Ils reviennent chaque année, et chacun contredit un chapitre précis.
La classe divine : un Main de six cents lignes qui fait tout, entouré de classes anémiques
réduites à des getters. C'est le chapitre 1 et le chapitre 3 ignorés — et la marque distinctive
est que les autres classes n'ont aucune méthode intéressante.
L'encapsulation absente : tout en public, y compris les attributs, parce que « c'est plus
simple pour y accéder depuis le Main ». Cela signale généralement le piège précédent.
L'héritage partout : une hiérarchie de six niveaux, parce qu'on a trouvé des attributs communs. Le test « est-un » du chapitre 5 en élimine la moitié, et la composition règle le reste.
Le grand assemblage final : voir la section précédente.
L'oubli du diagramme : le code est écrit d'abord, le diagramme dessiné la veille pour satisfaire à la consigne. Il ne sert alors à rien — et cela se voit.
Sur l'énoncé « un ouvrage peut exister en plusieurs exemplaires », faut-il une ou deux classes ?
Pourquoi implémenter par tranches verticales plutôt que classe par classe ?
À vous
L'exercice conduit l'analyse de l'énoncé d'ouverture, du texte au modèle.
D'abord l'extraction : le programme repère les noms et les verbes, et vous décidez, pour chacun, s'il devient une classe, un attribut, une méthode, ou rien. Le corrigé justifie chaque verdict — y compris les deux cas douteux, « pénalité » et « durée ».
Ensuite la vérification du modèle par un scénario : « Ana emprunte Dune, le rend avec cinq jours de retard ». Vous faites marcher le scénario sur le diagramme décrit en données, et le programme signale ce qui manque pour le mener à bout.
Enfin un contrôleur de conception, appliqué à deux versions du même projet : il compte les méthodes par classe, repère la classe divine, les attributs publics et les hiérarchies trop profondes. Vous verrez qu'un modèle correct et un modèle raté se distinguent à des chiffres simples.
Tirez un modèle d'un énoncé, éprouvez-le par un scénario, puis auditez deux conceptions.
const ENONCE = [ "Réaliser une application de gestion de bibliothèque.", "Les adhérents empruntent des ouvrages pour une durée limitée.", "Un ouvrage peut exister en plusieurs exemplaires.", "Les retards donnent lieu à une pénalité.", ].join(" "); // ── 1. Du texte aux candidats ───────────────────────────────────────────── const NOMS = ["bibliothèque", "adhérent", "ouvrage", "durée", "exemplaire", "retard", "pénalité"]; const VERBES = ["emprunter", "rendre", "calculer", "rechercher"]; // Pour chaque nom : "classe", "attribut", ou "à decider" (question à poser). const VERDICTS = { // ← à écrire, avec une justification en une ligne }; // ── 2. Le modèle, décrit en données ─────────────────────────────────────── const MODELE = { classes: { Adherent: { attributs: ["nom"], methodes: ["emprunter", "rendre"] }, Ouvrage: { attributs: ["titre", "auteur", "isbn"], methodes: [] }, Exemplaire: { attributs: ["cote", "etat", "disponible"], methodes: ["estDisponible"] }, Emprunt: { attributs: ["debut", "prevu", "effectif"], methodes: ["retardEnJours", "penalite"] }, }, relations: [ { de: "Adherent", vers: "Emprunt", mult: "0..*" }, { de: "Emprunt", vers: "Exemplaire", mult: "1" }, { de: "Exemplaire", vers: "Ouvrage", mult: "1" }, ], }; // Fait marcher un scénario sur le modèle et signale ce qui manque. function verifierScenario(modele, etapes) { const manques = []; for (const e of etapes) { const c = modele.classes[e.classe]; if (!c) { manques.push("classe absente : " + e.classe); continue; } // ← à écrire : vérifier que la méthode et les attributs requis existent } return manques; } const SCENARIO = [ { classe: "Adherent", methode: "emprunter", attributs: [] }, { classe: "Exemplaire", methode: "estDisponible", attributs: ["disponible"] }, { classe: "Emprunt", methode: "retardEnJours", attributs: ["prevu", "effectif"] }, { classe: "Emprunt", methode: "penalite", attributs: [] }, { classe: "Ouvrage", methode: "exemplairesLibres", attributs: [] }, // manque ? ]; // ── 3. Mesurer une conception ───────────────────────────────────────────── const PROJET_RATE = { Main: { methodes: 34, attributsPublics: 12, profondeur: 0 }, Livre: { methodes: 0, attributsPublics: 5, profondeur: 0 }, Adherent: { methodes: 0, attributsPublics: 4, profondeur: 0 }, LivrePoche: { methodes: 1, attributsPublics: 0, profondeur: 4 }, }; const PROJET_CORRECT = { Bibliotheque: { methodes: 6, attributsPublics: 0, profondeur: 0 }, Ouvrage: { methodes: 4, attributsPublics: 0, profondeur: 0 }, Exemplaire: { methodes: 5, attributsPublics: 0, profondeur: 0 }, Emprunt: { methodes: 5, attributsPublics: 0, profondeur: 0 }, Adherent: { methodes: 5, attributsPublics: 0, profondeur: 0 }, }; function auditer(nom, projet) { // ← à écrire : signaler la classe divine, les classes anémiques, les // attributs publics et les hiérarchies de plus de trois niveaux. console.log(" " + nom + " : à auditer"); } // ── À VOUS ──────────────────────────────────────────────────────────────── // 1. Remplissez VERDICTS pour les sept noms. // 2. Complétez verifierScenario : que manque-t-il pour mener le scénario ? // 3. Écrivez auditer() et comparez les deux projets. console.log(ENONCE);
En travaux pratiques
Rendre le projet, et savoir le défendre
Assembler la médiathèque des neuf séances en un projet livrable, testé, et dont chaque décision de conception peut être justifiée à l'oral.
- Les TP 1 à 9
- Un dépôt Git, et JUnit
- 1. L'inventaire
Listez ce que votre médiathèque fait aujourd'hui, et ce que l'énoncé demandait. Signalez explicitement les manques plutôt que de les laisser découvrir.
- 2. Séparer les couches
Réorganisez en trois paquetages : domaine, persistance, interface. Vérifiez qu'aucune classe du domaine n'importe rien de l'interface.
- 3. Tester le domaine
Écrivez des tests sur les règles métier : emprunt d'un document indisponible, quota d'adhérent, calcul de pénalité, retour. Un test par règle.
- 4. Tester les cas limites
Ajoutez les cas limites : catalogue vide, adhérent sans emprunt, retard de zéro jour, emprunt le jour même du retour, document rendu deux fois.
- 5. Persister
Sauvegardez et rechargez le catalogue dans un fichier. Vérifiez qu'un aller-retour complet redonne exactement l'état initial.
- 6. Le remplacement
Écrivez une seconde implémentation de la persistance, dans un autre format. Branchez-la SANS modifier une seule ligne du domaine.
- 7. Relire son propre code
Cherchez dans votre projet : un accesseur inutile, une cascade de tests de type, une classe qui fait deux choses, un catch vide. Corrigez ce que vous trouvez.
- 8. Préparer la soutenance
Pour trois décisions de conception, préparez la justification et l'alternative que vous avez écartée. C'est ce qui distingue un projet compris d'un projet recopié.
- Aucune classe du domaine n'importe quoi que ce soit de l'affichage ou du stockage
- Vos tests échouent si vous cassez volontairement une règle métier
- La seconde persistance se branche sans toucher au domaine
- Vous savez défendre trois choix et nommer ce que vous avez écarté
Ce que ce semestre laisse
Dix chapitres plus tôt, la question était de passer du programme qui calcule au programme qui modélise.
Le bloc I a montré ce qui casse sans objet, et donné la classe. Le bloc II l'a rendue responsable de sa propre cohérence. Le bloc III — le cœur — a permis de factoriser sans abuser du « est-un », puis d'écrire du code qui traite uniformément des objets de types différents. Le bloc IV a rendu tout cela robuste et dessinable, et le bloc V l'a fait vivre.
Reste une idée qui traverse les cinq blocs, et c'est peut-être ce qu'il faut en garder : la programmation objet consiste à décider où mettre chaque chose. Quelle classe porte quelle donnée, quelle classe porte quelle règle, ce qui est visible et ce qui ne l'est pas, ce qui est commun et ce qui varie. Le langage ne prend aucune de ces décisions à votre place — il se contente de rendre les bonnes faciles à écrire et les mauvaises faciles à regretter.
Et le critère de réussite est resté le même depuis le chapitre 1 : quand une exigence change, combien de fichiers faut-il ouvrir ? Un seul, si la chose était au bon endroit.
À retenir
Vous avez parcouru les 10 sections.
Marquez-la terminée pour faire avancer votre parcours, ou revenez sur un point avant de passer à la suite.