:: Enseignements :: Licence :: L3 :: 2020-2021 :: Programmation Objet avec Java ::
![[LOGO]](http://igm.univ-mlv.fr/ens/resources/mlv.png) | Un peu de design et des entrées-sorties |
Exercice 1 - Panier électronique
Voici ci-dessous des classes du package
fr.umlv.shopping permettant de modéliser des livres numériques
(avec un titre, un auteur et un prix) ainsi qu'un panier électronique dans lequel on peut ajouter
ou retirer des livres. Il est possible d'afficher le panier et de calculer son prix.
Book.java:
ShoppingCart.java:
La boutique qui vend les livres se diversifie et vend désormais d'autres types de contenu numérique:
des jeux-vidéo et des cartes cadeau dont on vous fournit le code.
Les jeux vidéo ont un titre, un type de console et un prix. Les cartes cadeau ont une valeur (entière)
et une durée de validité en semaines. Leur prix est calculé en fonction de ces 2 paramètres.
VideoGame.java:
PrePaid.java:
Les tests sont dans une classe du package
fr.umlv.shopping.test.
Test.java:
Le but de l'exercice est de transformer le code fourni pour que l'on puisse également ajouter
ces nouveaux types de contenu au panier. Et en réalité, la boutique prévoit déjà de vendre de la
musique et des films à l'avenir (dans un format qui reste à définir),
on aimerait donc que l'ajout de nouveaux média se fasse aisément par la suite.
-
Modifier le code fourni pour que l'on puisse ajouter/retirer aussi bien des livres que des
jeux vidéos et des cartes cadeau au panier. Vous pourrez tester en ajoutant simplement les
lignes suivantes au main de Test:
cart.add(zelda);
cart.add(pp50);
cart.add(pp100);
System.out.println(cart);
System.out.println(cart.price() + " euros cents");
Exercice 2 - Entrées-sorties
On veut maintenant pouvoir sauvegarder les articles d'un panier d'achats dans un fichier sous forme
textuelle en respectant un format particulier, afin de pouvoir les recharger en mémoire ultérieurement.
On retient le format de sauvegarde suivant (qui est volontairement un peu naïf). Chacun des
articles est représenté sur une seule ligne de texte. Cette ligne débute par
une chaîne de caractères précisant de quel type de contenu numérique il s'agit ("B" pour
un Book, "G" pour un VideoGame ou "P" pour une carte PrePaid).
Ensuite, séparés par un délimiteur (une chaîne de caractères, disons "#"), les différentes
caractéristiques de chacun de ces articles.
-
Ecrire les méthodes toTextFormat() nécessaires pour que le code
suivant produise l'affichage indiqué en commentaire. Attention, vous devez
encore essayer d'avoir du code générique en minimisant la duplication de code.
En particulier, on souhaiterait que tout type de média dispose de cette méthode
toTextFormat() qui retourne une String.
public class SaverLoader {
// ...
public static void main(String[] args) {
var sdb = new Book("S. de Beauvoir", "Mémoires d'une jeune fille rangée", 990);
System.out.println(sdb.toTextFormat());
// B#990#Mémoires d'une jeune fille rangée#S. de Beauvoir
var zelda = new VideoGame("The legend of Zelda", VideoGame.Console.WII, 4950);
System.out.println(zelda.toTextFormat());
// G#4950#The legend of Zelda#WII
var pp100 = new PrePaid(10000, 10);
System.out.println(pp100.toTextFormat());
// P#10000#10
}
}
Des constantes pour le délimiteur (dièse) et pour les types d'articles (B, G, P)
peuvent être définies dans la classe SaverLoader:
public class SaverLoader {
static final String SEPARATOR = "#";
static final String BOOK_TYPE = "B";
static final String VIDEO_GAME_TYPE = "G";
static final String PREPAID_TYPE = "P";
...
-
Écrivez maintenant dans la classe SaverLoader une méthode
saveInTextFormat qui prend en argument une liste d'articles numériques et un flot
d'écriture de caractères de type BufferedWriter; cette méthode écrit dans le flot
les chaînes de caractères produites par les méthodes toTextFormat
des différents articles de la liste, en les séparant par des retours à la ligne.
Ainsi, les articles créés à la question précédente doivent pouvoir être sauvegardés dans un fichier
par le code suivant (pensez à créer le chemin du fichier de sauvegarde):
var list = List.of(sdb, zelda, pp100);
Path saveFilePath = ... ; // nom du fichier de sauvegarde: "saveFile.txt"
try(var writer = Files.newBufferedWriter(saveFilePath,
StandardCharsets.UTF_8,
StandardOpenOption.CREATE)) {
SaverLoader.saveInTextFormat(list, writer);
}
Vérifier ce qui a été écrit dans le fichier!
-
Pour relire ce fichier, ou un autre formaté de la même manière, il nous faut maintenant une méthode
loadFromTextFormat qui prend en paramètre un flot de lecture de caractères et
retourne une liste d'articles numériques construits à partir des indications lues dans le flot.
Écrire cette méthode dans la classe SaverLoader.
Vous pourrez utiliser la méthode BufferedReader.readLine()
(ou son équivalent en stream BufferedReader.lines())
pour lire le flot ligne par ligne, et la méthode split() de la classe String
pour découper une ligne suivant un séparateur donné passé en argument.
Testez-la sur le fichier créé précédemment, en utilisant le try-with-ressources.
-
Le fichier ci-dessous contient des articles qui ont été sauvegardés en utilisant l'encodage
ISO-8859-15, extension de l'encodage Latin-1 qui inclut par exemple le symbole €.
Enregistrez ce fichier (en faisant clic-droit enregistrer-sous...) et réutilisez le code
que vous avez écrit à l'exercice précédent pour charger les articles de ce fichier.
-
En option pour les plus balèzes, en fait lorsque l'on a écrit loadFromTextFormat on a gentiment oublié
une contrainte énoncée plus tôt: la boutique risque de s'étendre et de proposer d'autres types de contenu
numérique, or notre solution utilise un switch, ce qui ne permet pas d'ajouter
le support de nouveaux contenus sans modifier le code du switch.
On peut améliorer le code en passant en paramètre de loadFromTextFormat une structure de donnée
qui permet d'associer le symbole de type utilisé pour le décodage à une fonction qui va effectuer le décodage.
Comme cela loadFromTextFormat va être paramétrée par l'ensemble des contenus numériques
possibles.
Le code d'appel de loadFromTextFormat sera alors le suivant
Map<String, ???> itemParserMap = Map.of(
BOOK_TYPE, parts -> new Book(...),
VIDEO_GAME_TYPE, parts -> new VideoGame(...),
PREPAID_TYPE, parts -> new PrePaid(...)
);
try(var reader = Files.newBufferedReader(saveFilePath, StandardCharsets.UTF_8))) {
var items = SaverLoader.loadFromTextFormat(reader, itemParserMap);
System.out.println(items);
}
avec ??? correspondant au type d'une fonction qui prend en paramètre un tableau des différentes
partie d'une ligne du fichier et qui renvoie un Book, un VideoGame, etc.
Dans un premier temps, quel est le type correspondant à ??? ?
Ensuite, compléter les lambdas permettant de décoder un livre, un jeu vidéo et une carte cadeau
pour initialiser correctement la Map.
Enfin ajouter une nouvelle méthode loadFromTextFormat qui prend deux paramètres, et qui décode
les lignes en fonction de la Map de fonctions passée en argument.
Exercice 3 - Réductions
Il arrive que certains clients commandent des articles en plusieurs exemplaires (au moins deux).
Dans ce cas, on applique une réduction de 5% à tous les articles concernés.
-
Comment peut-on faire pour gérer les multiplicités des articles de façon efficace ?
-
Modifier le code pour que le calcul du prix du panier prenne en compte cette réduction.
-
Pour aller plus loin: utiliser un Stream pour le calcul du prix d'un panier.
Vous verrez l'année prochaine comment optimiser également l'affichage.
© Université de Marne-la-Vallée