Une tirelire est un objet contenant des pièces de monnaie de différentes natures. Elle accepte l'insertion de certains types de pièces tandis que d'autres pièces peuvent être refusées. La tirelire a une capacité maximale en nombre de pièces. Il n'est pas possible de connaître son contenu sans la casser : une fois cassée elle révèle le montant global qu'elle contient (mais il n'est plus possible alors d'insérer des pièces).
On suppose que la tirelire accepte uniquement les pièces suivantes (que l'on ne représentera pas par un objet) de valeur en dessous de 1 euro :
- 1 centime
- 2 centimes
- 5 centimes
- 10 centimes
- 20 centimes
- 50 centimes
Préliminaires
Nous devons préalablement nous assurer de la présence (et installer si ce n'est pas le cas) sur notre machine de développement (qui peut tourner sous Linux, Windows ou MacOS) :
-
d'une version récente du Java Development Kit (JDK) ; notons que la présence du Java Runtime Environment (JRE) n'est pas suffisante car elle ne comprend que la JVM sans le compilateur (javac) ainsi que les bibliothèques d'API disponibles uniquement avec le JDK
- la version à support long terme la plus récente est la version 17 ; la version 18 est la dernière publiée
- le JDK peut être téléchargé ici et décompressé dans un répertoire ; sous Linux, il est conseillé de réaliser l'installation depuis le système de paquetages de la distribution (apt sous Debian/Ubuntu, pacman sous ArchLinux/Manjaro, dnf sous RedHat/Fedora...).
- de Maven que l'on utilisera pour construire les projets Java (téléchargeable ici ou depuis les paquetages de sa distribution)
- d'un environnement de développement (par example Eclipse ou IntelliJ IDEA), bien que le développement Java soit aussi praticable avec un éditeur de texte tel que vi (ou même le bloc-notes) ; l'expérience sera plus rustique, moins conviviale (pas d'autocomplétion) et nécessitera d'user d'un terminal pour écrire des commandes de compilation et d'exécution du code.
Tirelire simple
Modélisation UML de la tirelire
Modélisez la classe tirelire par un diagramme UML précisant les champs ainsi que les méthodes. On utilise un champ de type float pour conserver la somme en euros en tirelire (par exemple si nous ajoutons une pièce de 1 centime, nous ajoutons 0.01f au montant global).
Un nouveau projet Java avec Maven
Créez un nouveau projet à l'aide de Maven pour implanter la tirelire. Pour cela, nous exécutons la commande suivante afin d'utiliser l'archetype Maven le plus simple (quickstart) :
mvn archetype:generate -DgroupId=fr.uge -DartifactId=moneybox -DarchetypeArtifactId=maven-archetype-quickstart
Vous obtiendrez normalement un nouveau répertoire nommé moneybox contenant la structure de votre nouveau projet. Il ne reste plus qu'à commencer à coder. Pour cela, vous pouvez utiliser un éditeur de texte classique ou un environnement de développement tel qu'Eclipse ou IntelliJ IDEA qui devraient être capables d'ouvrir un projet créé avec Maven.
Remarque : l'archétype quickstart est un peu daté ; pour utiliser une version plus récente de Java et JUnit, vous pouvez copier les sections build et dependencies dans votre fichier de configuration Maven pom.xml en vous basant sur cet exemple.
Ecriture de la classe MoneyBox
-
Écrivez une classe Java remplissant le rôle de la tirelire (nommée MoneyBox). Il n'est pas nécessaire de connaître le contenu exact de la tirelire lorsqu'elle est cassée mais uniquement son montant global.
- N'oubliez pas que la tirelire est limitée en nombre de pièces ;
- qu'elle ne peut être cassée qu'une fois ;
- et qu'elle ne peut accepter que certains types de pièces.
Vous devrez lever des exceptions appropriées lorsque les méthodes rencontrent des cas problématiques.
UI d'utilisation de la tirelire
- Ecrivez un main qui crée une tirelire et utilise un Scanner (java.util) pour lire au clavier les valeurs des pièces à insérer dans la tirelire. La valeur -1 est utilisée pour casser la tirelire et afficher le montant contenu.
- Faites ce qu'il faut pour qu'en cas de pièce refusée, un message soit affiché mais que la saisie puisse continuer (gestion d'exception).
Ô voleur !
On suppose maintenant que nous disposons d'une tirelire d'une très grande capacité (par exemple 1 milliard de pièces). Ecrivez une méthode main dans une nouvelle classe pour initialiser une telle tirelire puis réalisez une boucle pour y insérer 1 milliard de pièces de 1 centime d'euros. Cassez la tirelire et récupérez le montant contenu. Le résultat obtenu est-il celui attendu ? Qui a volé l'argent déposé ? Comment peut-on résoudre ce problème ? On essaiera d'implanter une solution.
A titre d'information, Oracle avait prévu pour Java 9 d'intégrer une API monétaire dans la bibliothèque standard (JSR 354) mais cela n'a jamais été réalisé (il existe cependant une bibliothèque externe implantant la JSR 354).
Test de la tirelire avec JUnit
Afin de tester le comportement de la tirelire, nous avons créé une classe MoneyBoxTest contenant des tests unitaires JUnit que vous pouvez télécharger ici. Placez cette classe à l'endroit convenable dans votre projet. Vous pouvez ensuite exécuter les tests avec Maven à l'aide de la commande mvn test ou alors depuis l'interface graphique de votre environnement de développement.
Vérifiez si tous les tests passent correctement ; si ce n'est pas le cas, déterminez-en les raisons et faîtes les corrections qui s'imposent.
Tirelire évoluée
On souhaite maintenant créer une version plus évoluée de la tirelire. Celle-ci pourra connaître outre son montant global le nombre d'exemplaires de chaque type de pièces insérées.
- Copiez la classe MoneyBox écrite précédemment pour créer une nouvelle classe EnhancedMoneyBox
- Suivez le nombre d'exemplaires de chaque type de pièce insérée. On écrira une méthode int getCoinNumber(int coin) retournant le nombre de pièces d'un montant donné.
Pour stocker le nombre d'exemplaires des pièces, plusieurs options sont possibles. Pour l'instant on s'interdit l'utilisation du type Map<Integer, Integer> : nous n'avons le droit d'utiliser que le type int[] (tableau d'entiers). Quelle est la quantité de mémoire nécessaire pour stocker le tableau ?
Copier-coller MoneyBox pour créer EnhancedMoneyBox est une mauvaise pratique (cette solution ne peut être que transitoire). Nous devons factoriser le code commun entre MoneyBox et EnhancedMoneyBox. Pour cela nous décidons :
- de renommer MoneyBox en SimpleMoneyBox
- de créer un nouveau type MoneyBox qui sera ancêtre de SimpleMoneyBox et EnhancedMoneyBox qui regroupera le code commun à ces deux classes
- de modifier en conséquence SimpleMoneyBox et EnhancedMoneyBox
Réalisez le travail de factorisation de code demandé. Réfléchissez à la nature de MoneyBox : devra-t-on créer une classe abstraite ou alors une interface ? Devra-t-on modifier du code faisant référence à MoneyBox dans les méthodes main écrites précédemment ?
Racheteur de tirelire
Une fois qu'une tirelire est cassée, nous souhaiterions la faire racheter afin d'obtenir en échange des billets de banque (un peu plus légers que les pièces) ou alors créditer un compte bancaire.
Nous allons créer une interface MoneyBoxBuyer représentant un tel acheteur. Cette interface ne comporte qu'une seule méthode de signature int buy(MoneyBox mb) permettant de racheter une tirelire et retournant le nombre de centimes d'euros offerts pour cette tirelire. Si une tirelire n'est pas cassée, la méthode buy doit retourner une exception spécifique indiquant que la tirelire ne peut pas être rachetée en l'état.
Ecrivez une telle interface. Faîtes en sorte que l'implantation par défaut de cette méthode retourne la somme des pièces collectées dans la tirelire.
Une banque classique rachetant la tirelire s'en tient au comportement par défaut consistant à payer la somme exacte contenue dans la tirelire. Peut-on créer un acheteur banque simplement en créeant une instance de l'interface (MoneyBoxBuyer bank = new MoneyBoxBuyer()) ? Pourquoi ? Comment peut-on résoudre ce problème ?
Plutôt que de rapporter notre tirelire à une banque, nous souhaitons faire appel aux services d'un ferrailleur (ScrapDealer). L'avantage est que celui-ci peut donner plus d'argent que la valeur faciale des pièces en prenant en considération leur poids en métal (pour ensuite les fondre). Un ScrapDealer ne peut que racheter des EnhancedMoneyBox (sinon une exception est retournée).
Voici la masse de métal (cuivre) de chaque pièce :
- 1 centime : 2,30 g
- 2 centimes : 3,06 g
- 5 centimes : 3,92 g
- 10 centimes : 4,10 g
- 20 centimes : 5,74 g
- 50 centimes : 7,80 g
Il peut exister plusieurs instances de ScrapDealer qui peuvent différer par le cours du métal qu'ils appliquent. Ecrivez la classe ScrapDealer en conséquence.
Modifiez maintenant la méthode main de la première partie du TD : lorsque nous saisissons -1, la tirelire est cassée et elle est immédiatement vendue à un racheteur de tirelire. Deux racheteurs sont en concurrence : une banque classique et un ferrailleur qui rachète à un cours de métal de 10 €/kg. On affichera le montant de rachat pour la banque et le ferrailleur et on indiquera qui est le plus avantageux pour la tirelire que l'on vient de casser.
Si vous avez terminé, une suite est disponible sur cette page.