Programmation orientee objet
Concevoir des programmes autour d’objets, d’interfaces, de l’encapsulation et du polymorphisme.
APPROFONDI
Objectifs d’apprentissage
- Concevoir des classes Java et C++ qui encapsulent un état et préservent leurs invariants.
- Écrire des contrats de méthodes, des constructeurs sûrs et une gestion explicite des ressources.
- Choisir entre héritage et composition en argumentant la substituabilité et le couplage.
- Utiliser interfaces, classes abstraites et polymorphisme pour remplacer une implémentation sans casser le client.
- Tester et faire évoluer un modèle objet de gestion de bibliothèque avec des responsabilités séparées.
Prérequis utiles
- Maîtriser variables, fonctions, conditions, boucles, tableaux ou collections et compilation de base.
- Savoir lire une signature de méthode, manipuler des types et interpréter une erreur d’exécution.
- Connaître les bases de Java ou C++ : fichiers sources, visibilité élémentaire et tests simples.
Classes et encapsulation
Définitions et méthode
Une classe décrit un type : elle regroupe des attributs et des méthodes. Un objet est une instance créée à partir de ce modèle ; son état doit rester cohérent avec les règles du domaine. L’encapsulation consiste à cacher la représentation et à exposer des opérations contrôlées. En Java, on utilise souvent private puis des méthodes métier ; en C++, on complète avec un constructeur et des invariants vérifiés. Une erreur fréquente est de rendre tous les champs publics : le code appelant peut alors imposer un solde négatif ou une durée impossible.
Exemple résolu. Pour un Compte, l’invariant est solde >= 0. Un dépôt de 30 sur un solde initial de 70 donne 100 ; un retrait de 120 est refusé, donc le solde reste 100. La méthode retourne un succès ou une erreur plutôt que de modifier silencieusement l’état.
Point de contrôle : citer l’invariant, l’opération qui le préserve et le test qui vérifie le refus d’un retrait excessif.
- Une classe porte état, opérations et invariant.
- `private` protège l’état ; les méthodes valident les transitions.
- Un constructeur doit produire un objet immédiatement cohérent.
Une classe décrit un type : elle regroupe des attributs et des méthodes. Un objet est une instance créée à partir de ce modèle ; son état doit rester cohérent avec les règles du domaine.
Dossier complet — Programmation orientee objet
L2 · S3 · Programmation et systèmes
Dossier pédagogique original Matrice MI. Contenu d’entraînement non officiel; il ne remplace pas le support ou la maquette d’un établissement.
Objectifs d’apprentissage
-
Concevoir des classes Java et C++ qui encapsulent un état et préservent leurs invariants.
-
Écrire des contrats de méthodes, des constructeurs sûrs et une gestion explicite des ressources.
-
Choisir entre héritage et composition en argumentant la substituabilité et le couplage.
-
Utiliser interfaces, classes abstraites et polymorphisme pour remplacer une implémentation sans casser le client.
-
Tester et faire évoluer un modèle objet de gestion de bibliothèque avec des responsabilités séparées.
Prérequis
-
Maîtriser variables, fonctions, conditions, boucles, tableaux ou collections et compilation de base.
-
Savoir lire une signature de méthode, manipuler des types et interpréter une erreur d’exécution.
-
Connaître les bases de Java ou C++ : fichiers sources, visibilité élémentaire et tests simples.
Cours
1. Classes et encapsulation
Définitions et méthode
Une classe décrit un type : elle regroupe des attributs et des méthodes. Un objet est une instance créée à partir de ce modèle ; son état doit rester cohérent avec les règles du domaine. L’encapsulation consiste à cacher la représentation et à exposer des opérations contrôlées. En Java, on utilise souvent private puis des méthodes métier ; en C++, on complète avec un constructeur et des invariants vérifiés. Une erreur fréquente est de rendre tous les champs publics : le code appelant peut alors imposer un solde négatif ou une durée impossible.
Exemple résolu. Pour un Compte, l’invariant est solde >= 0. Un dépôt de 30 sur un solde initial de 70 donne 100 ; un retrait de 120 est refusé, donc le solde reste 100. La méthode retourne un succès ou une erreur plutôt que de modifier silencieusement l’état.
Point de contrôle : citer l’invariant, l’opération qui le préserve et le test qui vérifie le refus d’un retrait excessif.
Points-clés
- Une classe porte état, opérations et invariant.
privateprotège l’état ; les méthodes valident les transitions.- Un constructeur doit produire un objet immédiatement cohérent.
2. Héritage et composition
Choisir une relation entre classes
L’héritage exprime une spécialisation « est-un » : un LivreNumerique peut être un Document si tout code attendant un Document accepte le sous-type. La composition exprime « possède-un » : une Bibliotheque possède une collection de Document. Elle limite le couplage et facilite le remplacement d’une partie ; on la préfère lorsque la relation n’est pas une véritable spécialisation. Le principe de substitution impose de ne pas renforcer les préconditions ni affaiblir les garanties héritées. Une erreur fréquente consiste à utiliser l’héritage uniquement pour réutiliser quelques lignes.
Exemple résolu. Si Document.emprunter() garantit « disponible devient faux », un sous-type ne doit pas accepter une séquence qui laisse l’état incohérent. On peut plutôt injecter une politique DureeEmprunt dans Bibliotheque : la collection délègue le calcul sans multiplier les sous-classes.
Point de contrôle : pour « une bibliothèque contient des livres », justifier composition ; pour « un livre numérique est un document », vérifier le contrat partagé.
Points-clés
- « Est-un » suggère héritage ; « possède-un » suggère composition.
- La substituabilité porte sur les contrats, pas seulement sur les signatures.
- La composition réduit souvent le couplage et simplifie les tests.
3. Polymorphisme
Programmer contre un contrat
Une interface décrit les opérations promises sans imposer la représentation. Le polymorphisme permet de manipuler plusieurs implémentations via le même type abstrait. En Java, une référence d’interface peut viser toute classe qui l’implémente ; en C++, une classe abstraite possède souvent une méthode virtuelle pure (= 0) et un destructeur virtuel. La liaison dynamique choisit l’implémentation à l’exécution. Une erreur fréquente est d’oublier le destructeur virtuel en C++ ou de tester des détails internes plutôt que le contrat observable.
Exemple résolu. Empruntable expose emprunter() et retourner(). LivrePapier et LivreNumerique l’implémentent. Une liste d’Empruntable appelle emprunter() sans connaître la classe concrète ; si le livre est déjà prêté, chaque implémentation signale l’échec selon le même contrat. Le test vérifie le résultat et l’état, pas l’algorithme choisi.
Point de contrôle : distinguer type statique et type dynamique, puis expliquer comment une nouvelle implémentation s’ajoute sans modifier le parcours.
Points-clés
- Une interface stabilise un contrat indépendant de l’implémentation.
- Le dispatch dynamique sélectionne la méthode concrète.
- Les tests doivent cibler les garanties observables.
4. Constructeurs, contrats et durée de vie
Construire des objets fiables
Un constructeur initialise toutes les données nécessaires et refuse les paramètres invalides. Le contrat d’une méthode précise préconditions, effet et postconditions : par exemple, retirer(m) exige m > 0 et m <= solde, puis diminue le solde de m. En Java, le ramasse-miettes libère les objets devenus inaccessibles ; il ne faut pas compter sur finalize. En C++, la durée de vie est déterministe : le destructeur libère les ressources, et RAII associe leur possession à un objet. Une erreur classique est de retourner une référence vers une variable locale ou de copier naïvement une ressource possédée.
Exemple résolu. Une classe Fichier ouvre une ressource dans le constructeur, interdit la copie si elle est propriétaire, puis la ferme dans le destructeur. Avec un pointeur intelligent std::unique_ptr, la libération est automatique même lors d’une exception. En Java, try-with-resources joue un rôle comparable pour AutoCloseable.
Point de contrôle : expliquer qui possède chaque ressource et ce qui se passe si une méthode échoue au milieu.
Points-clés
- Les contrats rendent les erreurs observables et testables.
- RAII et les pointeurs intelligents sécurisent la propriété en C++.
- Java utilise notamment
try-with-resourcespour les ressources externes.
5. Conception testable et bonnes pratiques Java/C++
Organiser un modèle objet maintenable
Une conception robuste sépare domaine, persistance et interface utilisateur. Les méthodes courtes, les noms explicites et la responsabilité unique réduisent les effets de bord. Les exceptions ou résultats d’erreur doivent être cohérents et documentés ; il faut éviter de mélanger une erreur de validation avec une panne d’entrée-sortie. Pour comparer des objets, Java demande de respecter equals et hashCode ensemble ; C++ distingue égalité de valeur, ordre et identité. Les conteneurs standard (List, vector, map) évitent de réimplémenter une mémoire fragile.
Exemple résolu. Pour rechercher un livre par ISBN, Catalogue dépend d’une abstraction de dépôt. Un faux dépôt en mémoire fournit trois livres au test ; une recherche existante renvoie le bon objet, une recherche absente produit un résultat vide. Le même service fonctionne ensuite avec un dépôt fichier sans changer la règle métier.
Point de contrôle : repérer dans un diagramme la responsabilité du service, la dépendance remplaçable et le test qui prouve le cas absent.
Points-clés
- Séparer domaine, stockage et présentation limite les dépendances.
equals/hashCodeet l’égalité C++ doivent être cohérents.- Une dépendance injectable rend le code testable avec un faux composant.
Exercices corrigés
Exercice 1 · Fondamental
Écrire une classe Temperature dont le constructeur reçoit une valeur en degrés Celsius. Refuser une valeur inférieure à -273,15, fournir enFahrenheit() et conserver l’invariant de validité. Donner une version Java et préciser deux tests.
Correction détaillée
- Définir un champ
private final double celsiusen Java ; le constructeur testecelsius >= -273.15, sinon lèveIllegalArgumentException. 2. ImplémenterenFahrenheit()parcelsius * 9.0 / 5.0 + 32.0. 3. Testernew Temperature(0).enFahrenheit() == 32et vérifier qu’une construction avec-300échoue. 4. En C++, appliquer le même contrôle dans le constructeur et stocker undoubleprivé ; l’invariant est ainsi vrai après chaque construction.
Exercice 2 · Fondamental
Modéliser un Compte avec dépôt et retrait. Un dépôt et un retrait doivent être strictement positifs ; un retrait ne doit pas dépasser le solde. Décrire le comportement pour un solde initial de 100, un dépôt de 25, puis des retraits de 80 et 50.
Correction détaillée
- Le constructeur accepte 100 car le solde est non négatif. 2.
deposer(25)vérifie 25 > 0 puis produit 125. 3.retirer(80)vérifie 80 > 0 et 80 <= 125, puis produit 45 et signale le succès. 4.retirer(50)échoue car 50 > 45 ; le solde reste 45. 5. Un appel avec 0 ou une valeur négative est rejeté de la même manière. Le test doit vérifier à la fois le résultat d’échec et l’état inchangé.
Exercice 3 · Intermédiaire
Proposer un modèle pour une bibliothèque où Document expose un ISBN et une opération d’emprunt, tandis que LivrePapier ajoute un nombre de pages et LivreNumerique une taille de fichier. Justifier l’usage de l’héritage et écrire le scénario d’un second emprunt.
Correction détaillée
- Définir
Documentcomme classe abstraite avec ISBN privé, état disponible etemprunter/retourner. 2. Faire hériter les deux livres car chacun est substituable à un document et partage le contrat. 3. Conserver pages ou taille dans les sous-types, sans exposer les champs. 4. Le premieremprunterpasse disponible à faux et réussit. 5. Le second constate disponible = faux, échoue sans modifier l’état. 6. Un test via une référenceDocumentprouve le polymorphisme et l’invariant.
Exercice 4 · Intermédiaire
Une classe Catalogue doit chercher un document par ISBN sans dépendre du stockage. Définir une abstraction de dépôt, proposer un faux dépôt en mémoire et détailler les tests pour un ISBN présent, absent et dupliqué.
Correction détaillée
- Déclarer
DepotDocumentsavecchercher(isbn)et éventuellementenregistrer. 2. Injecter cette interface dansCatalogueplutôt que construire un fichier dans le service. 3. Le faux dépôt contient deux documents distincts ; pour l’ISBN présent, le catalogue renvoie l’unique document. 4. Pour l’ISBN absent, il renvoie un résultat vide ou une exception documentée, jamaisnullambigu. 5. Le faux dépôt doit refuser les doublons à l’enregistrement ; le test vérifie l’erreur et l’absence de seconde entrée. 6. Un dépôt fichier pourra être substitué sans changer les règles métier.
Exercice 5 · Avancé
Concevoir une politique d’emprunt extensible : un document papier est empruntable 21 jours, un document numérique 7 jours, et la politique doit être remplaçable au test. Éviter un switch sur le type concret. Expliquer interfaces, composition, invariants et tests.
Correction détaillée
- Définir
DureeEmpruntavecjoursPour(Document). 2. Injecter cette dépendance dans un serviceEmprunts, qui demande la durée puis crée un prêt ; le service ne connaît aucune sous-classe concrète. 3. Fournir une implémentation par règles, éventuellement basée sur une propriété abstraite du document, et une implémentation fixe pour le test. 4. Vérifier qu’un papier donne 21 jours et un numérique 7, dates comprises selon une convention explicitée. 5. Tester une politique fictive donnant 1 jour pour isoler le service. 6. Refuser un document déjà prêté et garantir qu’aucun prêt partiel n’est enregistré en cas d’échec.
Quiz d’autoévaluation
Question 1
Quel mécanisme protège directement un invariant d’objet ?
• Rendre tous les attributs publics ✓ Encapsuler l’état et valider les opérations • Dupliquer chaque attribut • Supprimer le constructeur
Explication : L’encapsulation limite les écritures et les méthodes peuvent vérifier les conditions avant de modifier l’état.
Question 2
Quelle relation correspond le mieux à « une bibliothèque possède des documents » ?
✓ Composition • Héritage obligatoire • Récursion • Surcharge
Explication : La bibliothèque contient des objets documents : c’est une relation de possession, donc une composition.
Question 3
Que garantit principalement une interface ?
• La représentation mémoire ✓ Un contrat d’opérations • Une allocation automatique • Une classe finale
Explication : Une interface décrit les opérations accessibles et leurs contrats, sans imposer la représentation interne.
Question 4
En C++, pourquoi un destructeur virtuel est-il important dans une base polymorphe ?
• Pour accélérer les boucles ✓ Pour choisir le bon destructeur via une référence de base • Pour rendre les champs publics • Pour éviter toute compilation
Explication : La destruction via un pointeur de base doit appeler aussi le destructeur du sous-type afin de libérer correctement ses ressources.
Question 5
Quel choix réduit le couplage dans un service de recherche ?
• Construire directement un fichier dans chaque méthode ✓ Injecter une abstraction de dépôt • Accéder aux champs de toutes les classes • Utiliser un type concret global
Explication : L’injection d’une abstraction permet de remplacer le stockage par un faux ou une autre implémentation.
Question 6
Une précondition de retrait est montant > 0. Que doit faire l’opération pour 0 ?
• Modifier quand même le solde • Accepter silencieusement ✓ Refuser selon le contrat • Changer de classe
Explication : Une entrée qui viole une précondition doit être rejetée de façon documentée et ne pas altérer l’état.
Question 7
Quel énoncé respecte la substituabilité ?
• Le sous-type renforce toutes les préconditions ✓ Le sous-type peut remplacer la base sans casser le client • Le sous-type supprime les garanties • Le client teste la classe concrète
Explication : La substituabilité signifie qu’un client utilisant le contrat de base continue de fonctionner avec le sous-type.
Question 8
Quelle association Java doit rester cohérente pour un objet utilisé dans une table de hachage ?
✓ equals et hashCode
• toString et le constructeur
• final et static
• public et private
Explication : Deux objets égaux doivent produire le même hashCode, sinon la recherche dans une collection hachée peut échouer.
Glossaire
- Encapsulation — Principe qui cache la représentation d’un objet et n’autorise les changements qu’au travers d’opérations contrôlées.
- Invariant — Propriété qui doit rester vraie après la construction et après chaque opération publique.
- Contrat — Description des préconditions, effets et postconditions attendus d’une méthode ou d’une interface.
- Composition — Construction d’un objet à partir d’autres objets dont il utilise les responsabilités et peut contrôler la durée de vie.
- Substituabilité — Propriété selon laquelle une instance d’un sous-type peut remplacer le type de base sans violer les attentes du client.
- Polymorphisme — Possibilité de traiter des objets de types concrets différents via un même contrat et de choisir l’opération adaptée.
- RAII — Idiom C++ associant acquisition et libération d’une ressource à la durée de vie d’un objet.
- Injection de dépendance — Technique qui fournit à un composant ses collaborations depuis l’extérieur afin de réduire le couplage et faciliter les tests.
TP · Mini-bibliothèque orientée objet testable
Construire une petite application en ligne de commande qui gère des documents, des adhérents et des prêts, avec stockage en mémoire. Le modèle doit distinguer livre papier et livre numérique, refuser les ISBN dupliqués et empêcher un second prêt simultané.
Objectifs
-
Modéliser des responsabilités par classes et interfaces.
-
Préserver les invariants d’ISBN, de disponibilité et de durée.
-
Démontrer le polymorphisme sans dépendre des classes concrètes.
-
Écrire des tests lisibles couvrant succès et erreurs.
Étapes
-
Définir les classes Document, LivrePapier, LivreNumerique, Adherent et Pret ; écrire leurs invariants.
-
Créer une abstraction Depot et une implémentation en mémoire, puis injecter ce dépôt dans le service.
-
Implémenter emprunt et retour avec une politique de durée remplaçable.
-
Ajouter au moins six tests : doublon, ISBN absent, premier prêt, second prêt, retour et recherche.
-
Rédiger un court diagramme de relations et exécuter le programme sur un scénario de trois documents.
Livrables attendus
-
Code Java ou C++ compilable accompagné d’un README de lancement.
-
Diagramme simple des classes et tableau des invariants.
-
Rapport de tests avec sorties, cas d’erreur et limites connues.
Critères d’évaluation
-
Les invariants sont vérifiés et les erreurs ne corrompent pas l’état.
-
Les responsabilités sont séparées et une abstraction est injectée.
-
Les tests couvrent les chemins nominaux et au moins trois refus.
-
Le code compile, est lisible et le README permet une reproduction.
Piste de résolution
Définir un Document abstrait avec ISBN et disponibilité, puis spécialiser les métadonnées papier et numérique. Injecter DepotDocuments et DureeEmprunt dans un service Emprunts. Refuser doublons, ISBN inconnus et prêts simultanés avant toute écriture ; enregistrer le prêt seulement après validation complète. Utiliser un faux dépôt pour les tests et une sortie CLI séparée du domaine.
Sujets originaux d’entraînement (non officiels)
Préparation originale — modèle objet et polymorphisme
Sujet original d’entraînement, non officiel : il ne s’agit jamais d’une épreuve d’années passées ni d’un document institutionnel. Justifier les choix et préciser les invariants.
Exercice 1
Concevoir les classes d’un service de prêt qui accepte livres papier et numériques. Donner deux invariants et expliquer composition ou héritage.
Correction : Créer un Document abstrait avec ISBN privé et disponibilité, puis deux sous-types car chacun est substituable au document et possède une donnée propre. La bibliothèque compose une collection de documents. Invariants : ISBN non vide et unique ; un document prêté ne peut pas être prêté une seconde fois. Les opérations valident avant mutation.
Exercice 2
Écrire le contrat d’une interface Depot et expliquer comment tester une recherche sans fichier.
Correction : Le contrat peut proposer Optional<Document> trouver(String isbn) et void ajouter(Document d), avec refus des ISBN vides ou dupliqués. Injecter une implémentation en mémoire contenant deux documents. Tester un résultat présent, un résultat vide et le refus d’un doublon ; le service ne connaît alors ni chemin ni format de fichier.
Exercice 3
Comparer en Java et C++ la gestion d’une ressource détenue par un objet.
Correction : En Java, utiliser AutoCloseable avec try-with-resources, qui ferme même lors d’une exception ; le ramasse-miettes ne fournit pas une date de libération fiable. En C++, appliquer RAII : constructeur acquiert, destructeur libère, et unique_ptr exprime une possession unique. Éviter une copie qui doublerait la libération ; préférer déplacement ou partage explicitement choisi.
Préparation originale — contrats et conception extensible
Sujet original d’entraînement, non officiel : il ne s’agit jamais d’une épreuve d’années passées ni d’un document institutionnel. Les solutions indiquent une démarche possible, pas un corrigé officiel.
Exercice 1
Un sous-type autorise un appel que la classe de base refusait et renvoie parfois null au lieu d’un résultat. Diagnostiquer la rupture de substituabilité et proposer une correction.
Correction : Le sous-type affaiblit ou modifie le contrat de façon imprévisible : le client de la base peut échouer. Stabiliser le contrat avec un résultat explicite ou une exception documentée, conserver les préconditions compatibles et garantir le même invariant. Si les comportements sont réellement différents, séparer les interfaces plutôt que forcer l’héritage.
Exercice 2
Proposer une conception sans switch pour appliquer 21 jours aux livres papier et 7 jours aux livres numériques, avec une politique testable.
Correction : Définir DureeEmprunt avec int joursPour(Document), puis l’injecter dans ServicePret. Fournir une implémentation de production et une politique fixe pour les tests. ServicePret dépend uniquement de l’interface ; il vérifie disponibilité, calcule la date et enregistre atomiquement. Les tests vérifient 21, 7 et une politique fictive à 1 jour.
Exercice 3
Expliquer pourquoi equals sans hashCode cohérent pose problème et donner un test révélateur.
Correction : Une collection hachée choisit un compartiment à partir du hashCode puis compare l’égalité. Si deux objets égaux ont des hash différents, ils peuvent être rangés dans des compartiments distincts et une recherche échoue. Implémenter les deux à partir de l’ISBN, puis insérer un objet dans un HashSet et vérifier qu’un nouvel objet égal est trouvé.
Parcours de révision sur quatre semaines
Semaine 1 · Classes, encapsulation et invariants
Relire le chapitre 1, écrire Temperature et Compte, puis ajouter des tests de préconditions et d’état inchangé. Avant de terminer, reformule les notions sans tes notes, vérifie les résultats dans le cours et note une difficulté précise à reprendre la semaine suivante.
Semaine 2 · Contrats, constructeurs et ressources
Annoter les préconditions/postconditions de cinq méthodes et comparer try-with-resources avec RAII sur un exemple court. Avant de terminer, reformule les notions sans tes notes, vérifie les résultats dans le cours et note une difficulté précise à reprendre la semaine suivante.
Semaine 3 · Héritage, composition et polymorphisme
Refaire le modèle Document, dessiner les relations, puis ajouter une implémentation d’interface sans modifier le client. Avant de terminer, reformule les notions sans tes notes, vérifie les résultats dans le cours et note une difficulté précise à reprendre la semaine suivante.
Semaine 4 · Conception testable et préparation
Réaliser le TP en temps limité, corriger les huit questions du quiz et rédiger un sujet d’entraînement avec tests justificatifs. Avant de terminer, reformule les notions sans tes notes, vérifie les résultats dans le cours et note une difficulté précise à reprendre la semaine suivante.