seance1_4h.md
-
Derrode Stéphane authoredDerrode Stéphane authored
Sommaire
BE #1 : Bibliothèque
Objectif du sujet
Dans cet énoncé, nous abordons deux concepts fondamentaux de la programmation orientée objet (POO), l'encapsulation et la composition, qui ont été vus lors du premier cours. Nous nous exercerons également aux diagrammes de classe du langage graphique UML. Pour vos propres diagrammes, vous pouvez utiliser diagrams.
Dans ce BE, il s'agit de concevoir et de réaliser un programme simple de gestion d'une bibliothèque, intégrant des lecteurs, des livres et des emprunts.
Remarque : Cet énoncé part d'un problème simple et connu qui permet d'en faire la conception et la réalisation dans le temps qui nous est imparti. Les choix de conception et de réalisation sont donc orientés par ces contraintes et par les objectifs pédagogiques, à savoir : apprendre la POO en Python. Il est clair que le même problème dans un cadre professionnel serait traité d’une autre manière et une solution basée sur des bases de données émergerait naturellement, solution que nous écartons a priori car en dehors du périmètre de ce cours.
Cahier des charges
Le cahier des charges de notre application est décrit ci-dessous. Il est volontairement donné de manière informelle.
On doit pouvoir gérer le fond documentaire d'une bibliothèque identifiée par son nom.
-
Notre application doit être capable de gérer des lecteurs. Chacun d’eux est caractérisé par :
- Son nom complet,
- Son adresse,
- Un numéro (entier positif attribué de manière unique par les bibliothécaires).
-
Pour simplifier, on considérera que tous les ouvrages sont des livres, caractérisés par :
- Le nom de l’auteur,
- Le titre de l’ouvrage,
- Un numéro de livre (attribué de manière unique par les bibliothécaires),
- Le nombre d’exemplaires disponibles.
-
On doit pouvoir ajouter un lecteur à une bibliothèque à tout moment et rechercher un lecteur (par son numéro de lecteur). On doit également pouvoir ajouter des ouvrages (acquisition de livres), et rechercher un ouvrage (par son numéro de livre).
-
Tout lecteur peut emprunter des livres dans une ou plusieurs bibliothèques. Un lecteur peut emprunter plusieurs livres différents simultanément ou à des dates différentes et un même livre peut être emprunté par plusieurs lecteurs (s’il existe plusieurs exemplaires). Au moment de l’emprunt, il faut donc vérifier qu’un exemplaire de l'ouvrage est bien disponible et qu'il n'a pas été emprunté par le même lecteur. De manière symétrique, il faut également gérer le retour des livres.
-
On désire également pouvoir éditer des états détaillés :
- Liste de tous les lecteurs d'une bibliothèque
- Liste de tous les livres d'une bibliothèque
- Liste de tous les emprunts d'une bibliothèque
-
Da manière facultative, on souhaite pouvoir retirer un lecteur (s'il n'a plus d'emprunt en cours), et retirer un exemplaire non emprunté d'un livre (désherbage ou vol). Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque.
Cet énoncé a pour objectif de vous accompagner pour répondre au cahier des charges, suivez le guide...
Remarque : Dans chaque titre de section, vous verrez figurer une durée entre parenthèses : celui-ci correspond au temps approximatif à passer pour répondre aux questions du chapitre. Si vous prenez trop de retard par rapport à ces milestones, demandez de l'aide à votre encadrant !
Remarque : Même si ce n'est pas pas obligatoire, il vous est demandé de développer chaque classe dans un fichier Python séparé (pensez à enregistrer tous les fichiers dans le même répertoire).
Classe Lecteur et classe Livre (75 minutes)
Point 1. du cahier des charges
-
Dessinez la boîte UML de la classe Lecteur, en respectant le cahier des charges ci-dessus et les conventions syntaxiques présentées en cours.
- Quels attributs devront être publiques, lesquels devront être privés ? Pour rappel : selon les principes de l'encapsulation, un attribut devra être privé si on veut/peut exercer un contrôle dessus.
- Prévoyez une méthode qui affiche l'état d'un lecteur. Pour rappel : l'état d'un objet est l'ensemble des valeurs de ses attributs à un instant donné.
-
Dans un fichier appelé Lecteur.py, implémentez votre classe progressivement, et développez simultanément le programme principal qui permet de vérifier le comportement attendu de votre classe (vu en cours : un programme principal commence par la commande
if __name__ == '__main__':
.
Pour être plus précis:- Écrivez en premier lieu le constructeur de votre classe et créez deux lecteurs dans votre programme principal. L'exécution du programme ne produit aucun résultat visible mais elle permet de vérifier que le programme est syntaxiquement correct. Ne passez pas à la méthode suivante tant que l'exécution produit des erreurs.
- Passez ensuite au codage de la méthode d'affichage (méthode dont le nom est
__str__
). Testez cette méthode en modifiant votre programme principal pour qu'il affiche les deux lecteurs précédemment créés. L'exécution du programme vous donne-t-elle satisfaction ?
-
Continuez de la même manière pour toutes les méthodes que vous avez prévues. Il est fort probable que nous soyons amenés à revenir sur les fonctionnalités de cette classe par la suite, pour la compléter avec de nouvelles méthodes et attributs.
Un programme principal typique aura l'allure suivante :if __name__ == '__main__': # Des lecteurs L1 = Lecteur('Mzai Ahmed', 'Boulevard de la Paix', 1) L2 = Lecteur('Xu John', 'Rue de la Gare', 2) L3 = Lecteur('Levgueni Dimitri', 'Impasse La Fayette', 3) L4 = Lecteur('Rodriguez Alfonso', 'Rue du Stade', 4) print('Lecteur 1 -->', L1) print('Lecteur 2 -->', L2) print('Lecteur 3 -->', L3) print('Lecteur 4 -->', L4)
Point 2. du cahier des charges
-
Réalisez la même démarche pour la classe Livre, que vous coderez dans le fichier Livre.py. Un programme principal typique aura l'allure suivante:
if __name__ == '__main__': # Des livres B1 = Livre('Le Père Goriot', 'Honoré de Balzac', 2, 101) B2 = Livre("Léon l'Africain", 'Amin Maalouf', 2, 102) B3 = Livre('Le Petit Prince', 'Antoine de Saint-Éxupery', 2, 103) B4 = Livre("L'Étranger", 'Albert Camus', 2, 104) print('Livre 1 -->', B1) print('Livre 2 -->', B2) print('Livre 3 -->', B3) print('Livre 4 -->', B4)
Classe Bibliothèque (75 minutes)
Points 3. et 5. (partiel) du cahier des charges
-
Un bibliothèque gère une collection de livres et un répertoire de lecteurs (et des emprunts, mais nous verrons cela plus tard). Quels types de lien entre les classes Bibliotheque et Livre d'une part, et entre les classes Bibliotheque et Lecteur d'autre part, vous semblent les plus adaptés pour exprimer ces relations ? Tip Si un livre appartient à une bibliothèque, un lecteur peut s'inscrire dans plusieurs bibliothèques (vous voyez la différence ?) ! Dessinez alors le schéma UML entre ces 3 classes. N'oubliez pas d'ajouter les cardinalités de part et d'autre des liens.
Dessinez ensuite le détail de la boîte UML de la classe Bibliothèque, de manière à répondre au point 3. du cahier des charges. -
Dans un nouveau fichier appelé Bibliotheque.py, commencez par coder le constructeur de la classe Bibliotheque, en choisissant la structure de données adéquate pour gérer les lecteurs et les livres. Créez un programme principal, dans lequel vous créerez une bibliothèque (de nom Michel Serre ?). Codez ensuite la méthode
__str__
qui affiche simplement le nom de la bibliothèque. Testez dans votre programme principal. -
On se concentre sur les lecteurs. Codez successivement l'implémentation des méthodes permettant
- d'ajouter un lecteur (on fera l'hypothèse que le numéro du lecteur est unique, sans vérification),
- d'afficher la liste des lecteurs de la bibliothèque,
- de chercher un lecteur par son numéro,
- de chercher un lecteur par son nom,
N'oubliez pas de tester chacune des méthodes avant de passer à la suivante ! N'hésitez pas à compléter la classe Lecteur s'il vous manque des méthodes...
-
Faites de même avec les livres. On ne vérifiera pas si le livre est déjà présent dans la collection avant de l'ajouter.
Les emprunts (90 minutes)
Points 4. et 5. (partiel) du cahier des charges
Un emprunt sera modélisé par un objet qui associe un lecteur (connu par son identifiant) avec un livre (lui aussi connu par son identifiant) à une date donnée.
-
Dessinez la boite UML de la classe Emprunt. Celle-ci doit permettre de créer un nouvel emprunt, et d'afficher son état. Prévoyez des getter (i.e. une méthode qui renvoie la valeur d'un attribut privé). Comment modéliser en UML les relations de la classe Emprunt avec les classes Lecteur et Livre ?
-
Implémentez votre classe dans un fichier appelé Emprunt.py. Complétez, en parallèle de l'implémentation des méthodes, un programme principal qui pourra finalement avoir l'allure suivante:
if __name__ == '__main__': ... # Création d'un emprunt entre un lecteur et un livre E1 = Emprunt(1, 100) print('Emprunt --> ', E1) print("Num lecteur de l'emprunt : ", E1.get_numero_lecteur())
Résultat:
E1 --> Emprunt - Numero lecteur : 1, Numero livre: 100, Date : 2020-08-23 Num lecteur de l'emprunt E1: 1
Remarque : Pour la date, on pourra utiliser l’instruction
date.isoformat(date.today())
, en ayant pris soin d'importer la librairie :from datetime import date
(à positionner tout en haut de la classe). -
À ce stade du développement, les emprunts sont indépendants de la bibliothèque. Compléter votre schéma UML précédent pour modéliser le fait que c'est la bibliothèque qui gère tous les emprunts. Quel nouvel attribut de la classe Bibliotheque prévoyez-vous pour stocker la collection des emprunts ?
-
Codez la méthode
emprunt_livre(...)
dans la classe Bibliotheque. Cette méthode est sensée- rechercher le lecteur à partir de son numéro,
- rechercher le livre à partir de son numéro et vérifier qu'il reste au moins un exemplaire à emprunter, et
- créer un nouvel objet
emprunt
que l'on stockera dans la liste des emprunts.
Tip : Pensez à
- modifier la classe Lecteur de manière à lui ajouter un compteur de livres empruntés (à sa création, un lecteur n'a pas d'emprunt). Prévoyez une méthode publique appelée
incremente_nb_emprunts()
. - modifier la classe Livre de manière à lui ajouter un compteur qui tient à jour le nombre d'exemplaires disponibles (à sa création, un livre à autant d'exemplaires disponibles qu'il a de nombre d'exemplaires). Prévoyez une méthode publique appelée
decremente_dispo()
qui décrémentera le nombre d'exemplaires disponibles et renverraTrue
s'il reste au moins un exemplaire, sinonFalse
. - modifier les méthodes
__str__
des 2 classes pour y inclure l'affichage de leur nouvel attribut.
Vous ajouterez également une méthode
affiche_emprunts()
pour visualiser la liste des emprunts et testerez vos nouvelles méthodes avant de passer à la suite. Pour tester votre méthode, ajouter ces quelques lignes à votre programme principal :if __name__ == '__main__': ... # Quelques emprunts print('\n--- Quelques emprunts :') b.emprunt_livre(1, 101) b.emprunt_livre(1, 104) b.emprunt_livre(2, 101) b.emprunt_livre(3, 101) print(b.chercher_livre_numero(101)) # Affichage des emprunts, des lecteurs et des livres print('\n--- Liste des emprunts :') b.affiche_emprunts()
et vérifiez que vous obtenez un affichage similaire à celui-ci
--- Quelques emprunts : ->Livre 101 - Emprunt impossible Livre - Nom auteur: Honoré de Balzac, Titre ouvrage: Le Père Goriot, \ Nb examplaires: 2, Numero: 101, Nb dispo: 0. --- Liste des emprunts : Emprunt - Numero lecteur : 1, Numero livre: 101, Date : 2020-08-23 Emprunt - Numero lecteur : 1, Numero livre: 104, Date : 2020-08-23 Emprunt - Numero lecteur : 2, Numero livre: 101, Date : 2020-08-23
-
Pour simuler le rendu d'un livre par un lecteur à la bibliothèque, implémentez une méthode retour_livre(self, numero_lecteur, numero_livre). Pour coder cette méthode, on pourra faire appel à une méthode privée appelée __chercher_emprunt(self, numero_lecteur, numero_livre) qui renverra l'instance de l'emprunt s'il fait partie de la liste des emprunts, ou None dans le cas contraire. La méthode retour_livre affichera un message d'erreur si l'emprunt n'existe pas. Dans le cas contraire, détruisez l'emprunt et pensez à mettre à jour le nombre d’exemplaires du livre ainsi que le nombre d’emprunts du lecteur.
Testez le retour dans votre programme principal grâce au code suivant :
if __name__ == '__main__': ... # Quelques retours de livres print('\n--- Quelques retours de livres :') b.retour_livre(1, 101) b.retour_livre(1, 102) b.retour_livre(10, 108)
qui devra afficher quelque chose de similaire à :
--- Quelques retours de livres : Aucun emprunt ne correspond a ces informations : 1 102 Aucun emprunt ne correspond a ces informations : 10 108
Crash-test
À ce stade, votre programme doit être fonctionnel, mais êtes-vous bien certains que l'ensemble de vos classes sont robustes ? Nous vous proposons de le vérifier à l'aide de ce crash-test, à télécharger à côté de vos fichiers.
Si, en lançant ce programme, vous obtenez des erreurs d'exécution, chercher à corriger vos méthodes. Les réponses attendues à chaque ligne de code sont données sous forme de commentaire dans le fichier. A vous de vérifier que tout est OK !
Remarque : Pour établir la robustesse de ses programmes, on utilise la programmation par tests et les tests unitaires...
Questions ouvertes supplémentaires
Point 6. (facultatif) du cahier des charges
-
Implémentez une méthode permettant de supprimer un livre (si tous les exemplaires sont rendus).
-
Implémentez une méthode permettant de supprimer un lecteur (si il n'est redevable d'aucun emprunt).
-
Un livre qui n'aurait plus d’exemplaire ne doit plus apparaître dans la liste des livres à disposition de la bibliothèque.
-
Comment implémenter un mécanisme de vérification de l'unicité de l'identifiant d'un lecteur et de celui d'un livre ?