Tout guerrier sait que les accessoires, épées, boucliers, etc., sont ce qui fait la différence entre la vie
et la mort sur un champ de bataille. Il y a donc un marché pour vendre ce genre d'accessoires.
Cela tombe bien, vous avez été recruté pour écrire le logiciel permettant de gérer les boutiques
d'accessoires des terres du milieu.
Une des particularités des terres du milieu est la monnaie. En effet, au lieu d'avoir une monnaie avec
une valeur unique, la monnaie est composée de pièces d'or (
gold), de pièces d'argent (
silver)
et de pièces de cuivre (
copper). Et bien sûr, on veut rester souple sur la monnaie car
un certain nombre d'achats et de ventes se fait encore en échangeant du tabac (
tobacco).
Pour cela, on va utiliser l'
enum Coin défini comme ceci
public enum Coin {
GOLD, SILVER, COPPER
}
sachant que, dans le futur, on pourrait vouloir ajouter la constante
TOBACCO.
-
Dans un premier temps, on va considérer que la monnaie est représentée uniquement par
des pièces d'or. Toutefois, pour l'affichage, on va quand même afficher les pièces d'or (g),
d'argent (s) et de cuivre (c). Pour l'instant, l'affichage se finira donc toujours
par "g 0s 0c".
On souhaite que la classe Money soit non mutable, que le constructeur sans paramètre
crée une monnaie correspondant à 0 pièce d'or et que la méthode add() permette
de créer une nouvelle monnaie en ajoutant un nombre strictement positif (non nul) de pièces d'or.
Écrire la classe Money de telle sorte que le code suivant fonctionne.
var money1 = new Money();
var money2 = money1.add(2, Coin.GOLD);
var money3 = money2.add(3, Coin.GOLD);
System.out.println(money1); // 0g 0s 0c
System.out.println(money2); // 2g 0s 0c
System.out.println(money3); // 5g 0s 0c
Note : si on veut que la classe soit non-mutable, cela veut dire que la valeur ne change
pas après la création, donc il vous faut aussi un constructeur privé qui prend en
paramètre le nombre de pièces d'or.
-
On souhaite maintenant définir une constante Money.ZERO qui correspond à 0 pièce
d'or et être capable de savoir si deux monnaies ont la même valeur avec la méthode
equals.
Modifier la classe Money de telle sorte que le code suivant fonctionne.
var money4 = Money.ZERO;
var money5 = Money.ZERO.add(7, Coin.GOLD);
System.out.println(money4.equals(new Money())); // true
System.out.println(money5.equals(Money.ZERO.add(7, Coin.GOLD))); // true
System.out.println(money5.hashCode() == Money.ZERO.add(7, Coin.GOLD).hashCode()); // true
Note : Le code précédent doit continuer à fonctionner sans modification.
-
On souhaite ajouter une méthode get(coin) qui permet de savoir combien de pièces
de chaque type (GOLD, SILVER, COPPER) sont présentes dans une monnaie
sachant que, pour l'instant, on ne peut toujours avoir que des pièces d'or.
Ajouter la méthode get(coin) de telle sorte que le code suivant fonctionne.
var money6 = Money.ZERO;
var money7 = Money.ZERO.add(3, Coin.GOLD);
System.out.println(money6.get(Coin.GOLD)); // 0
System.out.println(money7.get(Coin.GOLD)); // 3
System.out.println(money7.get(Coin.COPPER)); // 0
-
On souhaite maintenant gérer la notion de caddie (Caddy) et d'épée (Sword).
Une épée est définie par un nom (name, une chaîne de caractères) ainsi qu'un prix
(price, de type Money). Un Caddy est vide à la création.
On peut y ajouter des épées en utilisant la méthode add. L'affichage d'un caddie
affiche la liste des épées dans l'ordre d'appel de add, chaque épée sur une ligne.
L'affichage d'une épée affiche son nom et son prix avec le format visible ci-dessous.
En terme d'implantation, on vous demande que l'affichage d'un Caddy utilise un Stream.
Faire en sorte que le code suivant fonctionne.
var sword1 = new Sword("Skullcrusher", Money.ZERO.add(5, Coin.GOLD));
var caddy1 = new Caddy();
caddy1.add(sword1);
System.out.println(caddy1);
// sword Skullcrusher at 5g 0s 0c
Note : pour l'affichage, il n'y a pas retour à la ligne à la fin de l'affichage. C'est println
qui s'en charge.
-
On souhaite maintenant ajouter la possibilité d'ajouter des boucliers (Shield) à un
caddie. On veut de plus qu'il ne soit possible d'ajouter que des épées et des boucliers,
et pas d'autres choses, à un caddie. Un bouclier est défini par un booléen (small)
qui indique si un bouclier est petit ou normal (true = petit bouclier,
false = bouclier normal). En terme d'affichage, si un bouclier est petit l'affichage commence
par "small shield" tandis que si le bouclier est normal, l'affichage commence par "shield".
En plus du type de bouclier, l'affichage donne aussi le prix d'un bouclier avec le même format
que pour le prix d'une épée.
Modifier votre code de façon à ce que le code suivant fonctionne.
var sword2 = new Sword("Peacekeeper", Money.ZERO.add(4, Coin.GOLD));
var shield1 = new Shield(false, Money.ZERO.add(6, Coin.GOLD));
var caddy2 = new Caddy();
caddy2.add(sword2);
caddy2.add(shield1);
System.out.println(caddy2);
// sword Peacekeeper at 4g 0s 0c
// shield at 6g 0s 0c
-
On souhaite maintenant ajouter une méthode last qui renvoie le dernier accessoire (épée ou bouclier)
d'un caddie. Comme il peut ne pas y avoir de dernier accessoire, la méthode last
revoie un Optional.
Écrire la méthode last de telle sorte que le code suivant fonctionne.
var shield2 = new Shield(true, Money.ZERO.add(3, Coin.GOLD));
var sword3 = new Sword("Gutwrencher", Money.ZERO.add(5, Coin.GOLD));
var caddy3 = new Caddy();
caddy3.add(shield2);
caddy3.add(sword3);
System.out.println(caddy3.last());
// Optional[sword Gutwrencher at 5g 0s 0c]
-
On souhaite ajouter une méthode forEach(function) à Caddy, qui prend une fonction
en paramètre et appelle cette fonction avec chaque épée et bouclier d'un caddie dans l'ordre d'insertion.
Écrire le code de la méthode forEach(function) de telle sorte que le code suivant fonctionne.
var shield3 = new Shield(true, Money.ZERO.add(3, Coin.GOLD));
var sword4 = new Sword("Deathbringer", Money.ZERO.add(6, Coin.GOLD));
var caddy4 = new Caddy();
caddy4.add(shield3);
caddy4.add(sword4);
caddy4.forEach(System.out::println);
// small shield at 3g 0s 0c
// sword Deathbringer at 6g 0s 0c
-
On veut pouvoir faire la somme des prix des épées et boucliers d'un caddie, mais avant de faire
cela, nous allons ajouter une méthode sum dans Money pour faire la
somme de deux monnaies.
Écrire la méthode sum dans Money de telle sorte que le code suivant fonctionne.
var money8 = Money.ZERO.add(2, Coin.GOLD);
var money9 = Money.ZERO.add(3, Coin.GOLD);
var money10 = money8.sum(money9);
System.out.println(money10); // 5g 0s 0c
-
On souhaite maintenant ajouter une méthode price à un caddie qui fait la somme
des prix des épées et boucliers présents dans le caddie.
En terme d'implantation, on cherche à utiliser un Stream avec l'algorithme suivant :
pour chaque accessoire, on le transforme en son prix, puis on utilise la méthode
reduce à deux paramètres pour faire la somme de tous les prix.
var shield4 = new Shield(true, Money.ZERO.add(3, Coin.GOLD));
var shield5 = new Shield(true, Money.ZERO.add(3, Coin.GOLD));
var sword5 = new Sword("Souldrinker", Money.ZERO.add(7, Coin.GOLD));
var caddy5 = new Caddy();
caddy5.add(shield4);
caddy5.add(shield5);
caddy5.add(sword5);
System.out.println(caddy5.price()); // 13g 0s 0c
Note : si vous n'arrivez pas à utiliser un Stream, faites sans, mais je vais être déçu.
-
Dans le but de gérer non seulement les pièces d'or mais également toutes les autres pièces, on va dans un
premier temps à ajouter une méthode symbol et une constante COINS à l'enum
Coin.
En Java, les enum sont des sortes de classe, il peuvent donc contenir des champs ou des méthodes.
Pour déclarer des membres d'un enum, il faut ajouter un point-virgule (';') à la fin
de la liste des constantes. On peut déclarer les champs et méthodes juste après.
public enum Coin {
GOLD, SILVER, COPPER; // le ';' est important
// mettre les champs et les méthodes ici !
}
La méthode symbol renvoie un caractère qui correspond au type de pièce :
'g' pour GOLD, 's' pour SILVER et 'c' pour COPPER.
La constante COINS est une liste non modifiable des constantes de l'enum.
Écrire la méthode symbol et la constante COINS dans l'enum Coin
de telle sorte que le code suivant fonctionne.
System.out.println(Coin.GOLD.symbol()); // g
System.out.println(Coin.SILVER.symbol()); // s
System.out.println(Coin.COPPER.symbol()); // c
System.out.println(Coin.COINS); // [GOLD, SILVER, COPPER]
Note : il existe une méthode statique values() définie sur tous les enum et qui renvoie un
tableau des constantes définies dans l'enum (dans l'ordre de déclaration).
-
Enfin, on souhaite fournir une nouvelle implantation de Money qui sait gérer
les différents types de pièces. Pour cela, vous allez utiliser une table de hachage
(HashMap ou EnumMap à votre convenance) pour associer à un type de pièce
(Coin.GOLD, Coin.SILVER, etc) un entier qui indique combien de pièces de ce type existent.
Commentez l'ancienne implantation de Money et écrire une nouvelle implantation
utilisant une table de hachage en interne et telle que le code suivant (et tous les codes précédents)
fonctionne.
var money11 = Money.ZERO.add(1, Coin.SILVER).add(2, Coin.COPPER);
var money12 = Money.ZERO.add(2, Coin.GOLD).add(1, Coin.COPPER);
System.out.println(money11); // 0g 1s 2c
System.out.println(money12); // 2g 0s 1c
System.out.println(money12.get(Coin.GOLD)); // 2
System.out.println(money12.get(Coin.SILVER)); // 0
System.out.println(money12.get(Coin.COPPER)); // 1
System.out.println(money11.sum(money12)); // 2g 1s 3c
Note : si vous vous débrouillez bien, ajouter un nouveau type de pièce comme TOBACCO
devrait être aussi simple qu'ajouter la constante dans l'enum Coin, le reste du code devrait
s'adapter automatiquement.
Note 2 : attention à l'ordre d'affichage. Il ne dépend pas de l'ordre dans lequel on ajoute les pièces
mais de l'ordre dans lequel les constantes sont définies dans Coin.