:: Enseignements :: Licence :: L3 :: 2021-2022 :: Programmation Objet avec Java ::
[LOGO]

TP noté Java - 2021


Le but de ce TP noté est d'écrire quelques classes permettant de vérifier que vous maîtrisez la mise en œuvre des mécanismes de base pour la création de classes et d'objets, de délégation de responsabilité, de la création d'interface, de sous-typage et de la liaison tardive ainsi que l'utilisation de collections élémentaires pour la réalisation de petits algorithmes simples en Java.

À lire absolument

Tout ce que vous devez rendre devra obligatoirement être placé dans le répertoire EXAM à la racine de votre compte ; sinon, ce n'est pas récupéré et vous aurez 0.

La javadoc est accessible offline http://igm.univ-mlv.fr/~juge/javadoc-17/api/index.html.
Les pdf du cours sont accessibles https://www-igm.univ-mlv.fr/~forax/ens/java-avance/cours/pdf/.

Vous vous attacherez à toujours mettre les champs avec des modificateurs d'accessibilité les plus restreints possibles, à factoriser le code au maximum, de sorte à éviter autant que possible toute duplication de code et à ne pas ajouter de méthodes qui ne sont pas nécessaires par rapport à ce qui est demandé dans le sujet.

Vous écrirez toutes les classes de ce TP noté dans un package nommé fr.uge.manifest. Vous devez tester toutes les méthodes demandées et vous écrirez tous vos tests dans la classe Main du package fr.uge.manifest.main.
Attention, il est très important de respecter les noms des paquetages, des classes, des champs, des méthodes, l'ordre des paramètres que l'on vous demande : une partie de la correction est automatique.

Exercice 1 - Manifeste d'un porte conteneur

Un porte-conteneur (container en Anglais) est un bateau qui, comme son nom l'indique, transporte des conteneurs d'un port à l'autre. Chaque porte-conteneur possède un manifeste (manifest), qui est un document papier contenant une liste de l'ensemble des conteneurs qu'il transporte.
Le but de ce TP est de modéliser ce document papier.

  1. Dans un premier temps, on cherche à définir un Container. Un conteneur possède une destination sous forme de chaîne de caractères ainsi qu'un poids (weight en Anglais) qui est une valeur entière. Il ne doit pas être possible de créer un conteneur avec des valeurs invalides : la destination doit exister et le poids doit être positif ou nul.
    Écrire le type Container de telle façon à ce que le code suivant fonctionne :
      public static void main(String[] args) {
        var container = new Container("Germany", 500);
        System.out.println(container.destination());  // Germany
        System.out.println(container.weight());  // 500
      }
        
  2. On veut maintenant introduire la notion de Manifest, un manifeste contient une liste de conteneurs. Pour l'instant, un manifeste définit deux méthodes
    • add(conteneur) qui permet d'ajouter un conteneur au manifeste.
      Il ne doit pas être possible d'ajouter un conteneur null.
    • weight() qui renvoie la somme des poids des conteneurs du manifeste

    Écrire le type Manifest tel que le code suivant fonctionne :
      public static void main(String[] args) {
         ...
         var container2 = new Container("Italy", 400);
         var container3 = new Container("Austria", 200);
         var manifest = new Manifest();
         manifest.add(container2);
         manifest.add(container3);
         System.out.println(manifest.weight());  // 600
      }
        
  3. On souhaite maintenant pouvoir afficher un manifeste. Afficher un manifeste revient à afficher chaque conteneur sur une ligne, avec un numéro, 1 pour le premier conteneur, 2 pour le suivant, etc, suivi de la destination du conteneur ainsi que de son poids.
    Le formatage exact pour une ligne est :
          [numéro]. [destination] [poids]
        
    suivi d'un retour à la ligne (y compris après la dernière ligne).
    Modifier le type Manifest pour que le code suivant ait le comportement attendu :
      public static void main(String[] args) {
        ...
        var container4 = new Container("Spain", 250);
        var container5 = new Container("Swiss", 200);
        var manifest2 = new Manifest();
        manifest2.add(container4);
        manifest2.add(container5);
        System.out.println(manifest2);
        // 1. Spain 250
        // 2. Swiss 200
      }
        
  4. Sur le manifeste, un conteneur peut avoir un tampon (stamp) en forme d'étoile (star) pour indiquer que le conteneur a son prix qui sera réduit. On ne s'intéresse pas pour l'instant à la façon dont on calcule le prix d'un conteneur, on veut juste modéliser le fait que certains conteneurs ont un tampon et pas les autres.
    Pour cela, on va créer un type StarStamp qui représente un tampon sous forme d'étoile et modifier le code pour permettre de créer des containers avec un tampon sous forme d'étoile. On veut aussi que l'ancien code qui créé des conteneurs sans tampon puisse continuer à fonctionner. Lorsque l'on affiche un conteneur qui a un tampon sous forme d'étoile, une étoile apparait à la fin de la ligne d'affichage.
    Modifier le type Container pour que le code ci-dessous et les codes précédemment écrits dans le main fonctionnent.
       public static void main(String[] args) {
         ...
         var stamp = new StarStamp();
         var container6 = new Container("France", 550, stamp);
         var manifest3 = new Manifest();
         manifest3.add(container6);
         System.out.println(manifest3);
         // 1. France 550 *
    	
  5. On souhaite introduire un nouveau type de tampon, les tampons sous forme de plus (+). Dans le futur, on pourrait même vouloir introduire encore de nouvelles sortes de tampon. Un tampon sous forme de plus contient une catégorie (category) qui est un nombre entre 1 et 5 (les deux compris) et qui indique le nombre de plus sur le tampon. Il ne doit pas être possible de créer des tampons sous forme de plus avec une valeur de catégorie invalide.
    Lorsque l'on affiche un conteneur qui a un tampon sous forme de plus, autant de plus que la catégorie apparaissent à la fin de la ligne d'affichage : par exemple, si la catégorie est 2, deux '+' sont affichés.
    Créer un type PlusStamp qui représente les tampons sous forme de plus et faire en sorte que l'on puisse créer des conteneurs soit avec des tampons sous forme d'étoile, soit avec des tampons sous forme de plus, soit sans tampons.
    Vérifier que le code suivant fonctionne. Bien sûr le reste du main doit continuer à fonctionner :
      public static void main(String[] args) {
        ...
        var container7 = new Container("Venezuela", 150, new PlusStamp(1));
        var container8 = new Container("Liberia", 200, new StarStamp());
        var container9 = new Container("Alaska", 250, new PlusStamp(3));
        var manifest4 = new Manifest();
        manifest4.add(container7);
        manifest4.add(container8);
        manifest4.add(container9);
        System.out.println(manifest4);
        // 1. Venezuela 150 +
        // 2. Liberia 200 *
        // 3. Alaska 250 +++
      }
        
  6. Il arrive que l'on soit obligé de décharger tous les conteneurs liés à une destination s'il y a des problèmes de frais de douane. Dans ce cas, il faut aussi supprimer tous les conteneurs liés à cette destination au niveau du manifeste.
    Pour prendre en compte cela, on introduit une méthode removeAllContainersFrom(destination) qui supprime tous les conteneurs liés à une destination. S'il n'y a pas de conteneur pour cette destination, on ne fait rien.
    Modifier le code pour introduire cette méthode pour que l'exemple ci-dessous fonctionne :
      public static void main(String[] args) {
        ...  
        var container10 = new Container("Portugal", 450);
        var container11 = new Container("China", 200);
        var container12 = new Container("Portugal", 125);
        var manifest5 = new Manifest();
        manifest5.add(container10);
        manifest5.add(container11);
        manifest5.add(container12);
        manifest5.removeAllContainersFrom("Portugal");
        System.out.println(manifest5);
        // 1. China 200
      }
        
  7. On souhaite maintenant pouvoir calculer le prix qu'un client va payer pour mettre ses conteneurs sur le porte-conteneur. Le prix dépend de la destination de chaque conteneur. Pour cela, on va ajouter une méthode price qui prend comme premier paramètre un dictionnaire qui associe un prix à chaque destination, et comme second paramètre le prix à utiliser si la destination n'a pas de prix associé dans le dictionnaire.
    Par exemple, avec le code suivant, le premier conteneur a pour destination "Scotland", et le dictionnaire indique que ce conteneur a un prix de 40. Pour le second conteneur, la destination est "Ireland", et le prix associé est de 50. Enfin, pour le dernier conteneur, "Russia" n’apparaît pas dans le dictionnaire, donc le prix est celui par défaut, c'est-à-dire 100 (le deuxième paramètre). Le prix total est 40 + 50 + 100 = 190.
      public static void main(String[] args) {
        ... 
        var container13 = new Container("Scotland", 200);
        var container14 = new Container("Ireland", 100);
        var container15 = new Container("Russia", 200);
        var manifest6 = new Manifest();
        manifest6.add(container13);
        manifest6.add(container14);
        manifest6.add(container15);
        var price = manifest6.price(Map.of("Scotland", 40, "Ireland", 50), 100);
        System.out.println(price);   // 190
      }
        

    Note : attention à bien vérifier que le dictionnaire pris en paramètre n'a que des valeurs valides !
  8. En fait, le calcul du prix est un peu plus compliqué, car il ne dépend pas uniquement de la destination mais aussi des tampons. Dans le cas où l'on a un tampon sous forme d'étoile, il y a une réduction sur le prix de 200 (uniquement sur le prix du conteneur, un conteneur ne peut pas avoir un prix négatif !). Dans le cas où l'on a un tampon sous forme de plus, le conteneur à une réduction de prix de 10% par plus (par exemple ++++ correspond à une réduction de 40%).
    Dans l'exemple ci-dessous, le "Laos" ne fait pas partie des destinations du dictionnaire donc le prix est 400, le "Vietnam" non plus n'est pas dans le dictionnaire, donc le prix est 400 mais comme il y a un tampon sous forme d'étoile, le prix est 400 - 200 = 200, pour la "Thailand" a un prix de 100 dans le dictionnaire mais comme il y a un tampon ++, le prix est réduit de 20%, donc 100 - 100 * 20 / 100 = 80. Le prix total est 400 + 200 + 80 = 680.
      public static void main(String[] args) {
        ...
        var container16 = new Container("Laos", 120);
        var container17 = new Container("Vietnam", 120, new StarStamp());
        var container18 = new Container("Thailand", 120, new PlusStamp(2));
        var manifest7 = new Manifest();
        manifest7.add(container16);
        manifest7.add(container17);
        manifest7.add(container18);
        var price7 = manifest7.price(Map.of("Thailand", 100), 400);
        System.out.println(price7);  // 680
      }
        
  9. On met les conteneurs ayant la destination au même endroit sur le porte-conteneur, et si un porte-conteneur est mal équilibré il a une fâcheuse tendance à se retourner. Donc, pour aider au placement des conteneurs, il doit être possible de fournir un dictionnaire qui, pour chaque destination, indique le poids de l'ensemble des conteneurs liés à cette destination.
    Pour cela, écrire une méthode weightPerDestination qui, pour un manifeste donné, renvoie un dictionnaire qui indique le poids des conteneurs pour chaque destination.
    Par exemple, avec le code ci-dessous, il y a deux conteneurs qui ont comme destination "Monaco", avec un poids combiné de 100 + 300 = 400, tandis que "Luxembourg" a un seul conteneur de poids 200.
      public static void main(String[] args) {
        ...
        var container19 = new Container("Monaco", 100);
        var container20 = new Container("Luxembourg", 200);
        var container21 = new Container("Monaco", 300);
        var manifest8 = new Manifest();
        manifest8.add(container19);
        manifest8.add(container20);
        manifest8.add(container21);
        System.out.println(manifest8.weightPerDestination());
        // {Monaco=400, Luxembourg=200}
      }