TP 5-6 — Héritage et hiérarchies
Construire ses premières hiérarchies de classes avec des tests JUnit fournis : bibliothèque et articles périssables, puis salaires d'une entreprise et revente de véhicules de collection.
Objectif des TP : construire ses premières vraies hiérarchies de classes — factorisation, extension, spécialisation — avec, pour la première fois, des classes de tests JUnit fournies qui valident chaque étape d'un coup.
TP 5 — Fond documentaire : Book, Library, ComicBook
La progression impose de poser Book seul avant d'introduire l'héritage : cote, titre,
auteur, année — l'énoncé demande de réfléchir à l'accessibilité et à la visibilité de
chaque attribut avant d'écrire accesseurs et mutateurs.
Librarygère un catalogue avecgetBook(String code),addBook,removeBooksurchargé (par cote ou par objet) ettoString. Les affichages attendus du sujet servent de spécification, par exempleBook [H2G2:The Hitchhiker's Guide to the Galaxy->D. Adams,1979].- Les emprunts ajoutent à
Bookun attributborrower(0 = disponible),borrow(int borrower),giveBack()etisAvailable();Librarydélègue avecborrow(String code, int borrower),giveBack(String code)et liste les emprunts dansborrowings(). Le scénario à deux emprunteurs (42 et 404) montre l'échec d'un double emprunt. ComicBook extends Book: un attributillustratorde plus, mais une manipulation identique à celle d'un livre — on l'ajoute au catalogue et on l'emprunte pareil. C'est tout l'intérêt de l'héritage : la bibliothèque ne change pas.- Les durées créent la spécialisation : 15 jours d'emprunt pour un livre ; pour une
bande dessinée, 5 jours si elle est parue il y a 2 ans ou moins, sinon 15. Un
attribut
borrowingDateapparaît, etgetDurationMax()/getGiveBackDate()sont redéfinies dansComicBook— le même appel sur des référencesBookproduit des dates de retour différentes selon le type réel.
La classe de tests LibraryTest est fournie : elle rejoue tous les scénarios.
TP 5 — Inventaires : Article, Shop, PerishableArticle
Article porte une référence, un label, un prix d'achat final (non modifiable après
création) et un prix de vente ; le constructeur sans prix de vente applique la marge du
magasin (0.2, soit 20 %) — deuxième surcharge de constructeur du TP. getMargin()
calcule la marge brute, l'affichage attendu est
Article [42err404:chaise=20.0e/24.0e].
PerishableArticle extends Article ajoute une deadLine (par défaut à 10 jours de la
date courante) et quatre constructeurs couvrant toutes les combinaisons de défauts.
La classe Shop gère la liste d'articles, avec :
getPerishables()pour filtrer les périssables — une méthode supportisPerishable()dansArticle(fausse par défaut, vraie dans la sous-classe) évite toutinstanceof;discountPerishable(LocalDate threshold, double rate): démarque des périssables dont l'échéance arrive avant le seuil, etdiscountNotPerishable(double rate)pour les soldes des non-périssables ;mostProfitable(): le produit le plus rentable, quelle que soit sa nature — encore un traitement générique écrit une fois sur le type parent.
La classe de tests ShopTest est fournie.
TP 6 — Salaires : une hiérarchie abstraite
La hiérarchie demandée compte 6 classes dont 2 abstraites : Employee (abstraite) →
Salesperson (abstraite) → Vendor et TravellingSalesperson, plus Worker.
Le motif central est getTitle() : toString() est écrit une fois dans Employee
et invoque getTitle(), redéfini partout — le nom est générique, le type d'employé est
spécifique. Les commissions se factorisent dans Salesperson (20 % du chiffre
d'affaires) ; les bonus deviennent des constantes : 400 € pour le vendeur, 800 € pour le
représentant en raison de ses déplacements, 5 € par unité produite pour l'ouvrier.
Company gère le personnel (addEmployee, removeEmployee surchargé), compte par
catégorie (getNbEmployee, getNbSalesPerson, getNbWorker) et licencie avec
firing(LocalDate fatefulDate) les derniers embauchés. La variante introduit le
statique : un attribut de classe objective (10 000.0 pour les commerciaux, 1 000
pour les ouvriers), une méthode objectiveFulfilled() placée là où il faut, et la
surcharge firing() sans paramètre qui licencie tous les insuffisants. La classe de
tests CompanyTest est fournie.
TP 6 — Revente de véhicules : masquer puis redéfinir
Car distingue les attributs constants (marque, date de première mise en circulation,
prix d'achat) des variables (mise en vente, prix de vente, kilométrage) et surtout des
variables de classe : temporalDropRate (0.001 par mois) et mileageDropRate
(0.002 tous les 1000 km) — fixées légalement, donc identiques pour toutes les voitures,
donc static. computeDropRate() cumule les deux décotes, priceEstimation() applique
le taux au prix d'achat, et getSalePrice() réestime à chaque appel pour tenir compte
des changements.
VintageCar extends Car masque temporalDropRate pour lui donner une valeur nulle
— pas de décote temporelle sur une collection. Le scénario des deux voitures de mêmes
caractéristiques fait alors le démonstrateur du cours 6 : après masquage seul, les prix
restent identiques, et ce n'est qu'en redéfinissant les accesseurs des variables de
classe que le calcul change enfin. La leçon : un attribut masqué ne disparaît pas, il
faut aussi redéfinir les méthodes d'accès.
Dealership ferme la marche : ses méthodes de classe createCar() fabriquent une
VintageCar si le véhicule a au moins 40 ans, une Car sinon, en conservant la
souplesse des valeurs par défaut des constructeurs. L'utilisateur ne crée plus
d'instance directement — impossible de choisir VintageCar au lieu de Car pour
minimiser la décote et gonfler le prix de vente.
Redéfinir ou masquer ?
Les méthodes se redéfinissent (lookup dynamique à l'exécution) ; les attributs ne peuvent que se masquer (accès résolu par le type effectif). Le TP 6 est celui où cette distinction change le résultat à l'exécution — ne le bâclez pas.
Parce que l'héritage garantit qu'une bande dessinée est aussi un Book : le
catalogue stocke des références vers le type parent et tout traitement générique
(emprunt, affichage) fonctionne sans modification — c'est le polymorphisme par
héritage.
En filtrant le catalogue avec isPerishable() avant d'appliquer le rabais : la
méthode support dans Article remplace tout test de type, et chaque sous-classe
répond pour elle-même — même logique que instanceof, en plus propre.
Parce qu'elles ne modélisent aucun emploi réel : getWages() ne se calcule pas sans
savoir de quel type d'employé il s'agit. Elles factorisent l'état et le comportement
commun, et interdisent d'instancier un « employé générique ».
Le masquage crée un second champ temporalDropRate dans VintageCar, mais le code
qui calcule la décote continue de lire le champ de Car selon le type effectif de la
référence. Seule la redéfinition des accesseurs fait intervenir le lookup
dynamique et change le calcul.
tpOO_05.pdf
R2.01 · TP 5 — Fond documentaire / inventaires · 4 pages
Chargement du document…
tpOO_06.pdf
R2.01 · TP 6 — Calcul de salaires / revente de véhicules · 4 pages
Chargement du document…
Dernière mise à jour le
TP 3-4 — Énums et encapsulation
Card et Task avec énumérations et commits GitLab, ToDoList qui grandit de 5 en 5 — puis encapsulation en cascade (Person/Student/StudentAbs/YearGroup) et réutilisation d'un jar documenté.
TP 7-8 — Collections
Choisir la bonne structure de données : annuaire bidirectionnel à deux HashMap, élevage de volailles en Collection, mise en rayon avec Comparable et viviers de participants en HashSet.