:: Enseignements :: ESIPE :: E3INFO :: 2021-2022 :: Programmation Objet avec Java ::
[LOGO]

TP noté Java


Le but de ce TP noté est d'écrire quelques classes permettant de vérifier que vous maitrisez la mise en oeuvre des mécanismes de base pour la création de classes et d'objets, de composition, de délégation et de responsabilité, de sous-typage par interface, 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 en local en lançant la commande jdoc depuis un terminal.
Les pdf du cours sont accessibles ici.

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écessaire par rapport à ce qui est demandé dans le sujet.

Vous écrirez toutes les classes de ce TP noté dans un package nommé fr.umlv.arthur. Vous devez tester toutes les méthodes demandées et vous écrirez tous vos tests dans la classe fr.umlv.arthur.test.Test d'un sous-package.
Attention, il est très important de respecter les noms des paquetages, des classes, des champs et des méthodes que l'on vous demande : une partie de la correction est automatique.

Exercice 1 - Le roi Arthur

Au lieu de se battre sur un champ de bataille, le roi Arthur a décidé qu'il suffisait d'être le meilleur dans un monde alternatif pour remporter une bataille; cela fait verser moins de sang et cela permet de passer plus de temps avec Genièvre.
On va modéliser ce monde alternatif.

  1. On cherche dans un premier temps à créer une classe Sword non mutable représentant une épée ainsi qu'une classe Knight représentant un chevalier. Un chevalier possède une méthode damage qui correspond aux dégâts qu'il est capable d'infliger en utilisant son épée (ce sont donc les dégâts de son épée).
    Par exemple, pour créer Arthur, on va créer une épée de nom (name) excalibur qui inflige 10 de dégât (damage) ainsi qu'un chevalier de nom (name) arthur qui possède une épée (sword), de sorte que le main ci-dessus dans fr.umlv.arthur.test.Test fonctionne comme illustré dans les commentaires.
      public static void main(String[] args) {
        var excalibur = new Sword("excalibur", 10);
        var arthur = new Knight("arthur", excalibur);
        System.out.println(excalibur.name());    // affiche: excalibur
        System.out.println(excalibur.damage());  // affiche: 10
        System.out.println(excalibur);           // affiche: excalibur
        System.out.println(arthur.name());       // affiche: arthur
        System.out.println(arthur.damage());     // affiche: 10
      }
        

    De plus, deux épées sont les mêmes si elles ont le même nom ET font les mêmes dégâts.
      public static void main(String[] args) {
        ...
        var excalibur2 = new Sword("excalibur", 10);
        System.out.println(excalibur.equals(excalibur2));     // true
      }
        
  2. Attention: si vous ne l'avez pas déjà fait, pensez à gérer correctement le fait qu'un chevalier ou une épée doivent forcément avoir un nom, qu'une épée ne peut pas faire des dégâts négatifs ou nuls, et qu'un chevalier ne peut pas être créé sans lui fournir d'épée.
    De plus, on veut garantir qu'il n'est pas possible d'hériter ni de Sword, ni de Knight.
  3. On souhaite maintenant représenter le chevalier Lancelot qui a pris l'habitude dans ce monde alternatif de se battre avec deux épées, une dans chaque main. Il est donc capable d'infliger des dégâts correspondant à la somme des dégâts de ses deux épées en même temps.
    Faites les modifications qui s'imposent pour que le code suivant fonctionne (et que le code précédent continue de fonctionner)
      public static void main(String[] args) {
        ...
        var secace = new Sword("secace", 7);
        var seure = new Sword("seure", 4);
        var lancelot = new Knight("lancelot", secace, seure);
        System.out.println(lancelot.damage());     // 11
      }
        
  4. Ajouter une méthode isBetterThan qui permet de tester si le chevalier sur lequel on l'appelle inflige plus de dégâts (strictement) que celui passé en argument.
    Dans notre monde, Lancelot est un meilleur chevalier que Arthur. Modifier aussi le main pour vérifier cela.
  5. On souhaite manitenant ajouter Mordred (le neveu d'Arthur) à notre modélisation. Mordred ne sait pas se battre avec deux épées comme Lancelot mais il sait se battre avec une épée et un bouclier. Il inflige donc des points de dégâts (damage) mais aussi sait se protéger en utilisant des points de protection (protection).
    Un bouclier (Shield) doit être, comme épée (Sword), une classe non modifiable.
    La définition d'un meilleur chevalier change aussi: si deux chevaliers infligent les mêmes dégâts, alors le meilleur chevalier est celui qui a le plus de protection.
      public static void main(String[] args) {
        ...
        var hydra = new Shield("hydra", 4);
        var clarent = new Sword("clarent", 7);
        var mordred = new Knight("mordred", hydra, clarent);
        System.out.println(mordred.damage());  // 7
        System.out.println(mordred.protection());  // 4
      }
        

    Note: au lieu de créer autant de constructeurs que combinaisons de boucliers et d'épées, considérez que l'on peut introduire un type Equipment qui est soit une épée soit un bouclier. De plus, vous pouvez stocker les équipements dans une liste pour faciliter le traitement.
  6. On souhaite maintenant pouvoir afficher un chevalier suivant un format précis, par exemple, pour Arthur
          arthur damage: 10 protection: 0
            [excalibur]
        
    ou pour Mordred
          mordred damage: 7 protection: 4
            [hydra, clarent]
        

    Attention, il y a deux espaces avant les équipements et l'ordre des équipements doit être le même que l'ordre lors de la création.
  7. Dans notre monde alternatif, Shiva la déesse à 4 bras est aussi un chevalier. Elle utilise une épée, un bouclier mais aussi deux arcs (Bow) qui infligent des dégâts et permettent de se protéger.
    On souhaite changer l'implantation pour accepter qu'un chevalier ait n'importe quel nombre de bras. Faites les changements qui s'imposent.
      public static void main(String[] args) {
        ...  
        var astra = new Sword("astra", 20);
        var bow1 = new Bow("bow1", 10, 7);  // bow1 est un Bow de name "bow1", 
                                            // de damage 10 et de protection 7
        var bow2 = new Bow("bow2", 10, 7);
        var shield = new Shield("shield", 12);
        var shiva = new Knight("shiva", astra, bow1, bow2, shield);
        System.out.println(shiva);  //affiche: shiva damage: 40 protection: 26
                                    //           [astra, bow1, bow2, shield]
      }
        

    Note: Vous pouvez laisser les parties de votre ancien code que vous changez en commentaire si vous n'êtes pas sûr de vous.
  8. En fait, plus un chevalier s'entraine, mieux il utilise ses équipements. Pour prendre cela en compte, on souhaite avoir un bonus d'héroicité. Comme on est pas trop sûr de comment modéliser un bonus, on va introduire une classe Heroicity et faire en sorte que la décision de savoir si le bonus est additif (genre +3) ou multiplicatif (genre *3) soit uniquement codé dans la classe Heroicity et pas dans une autre classe.
    Pour l'instant nous allons implanter Heroicity de façon à appliquer le même facteur multiplicatif sur les dégâts et les protections. Le bonus doit être compris entre 1 et 3; s'il vaut 1, le chevalier n'est pas héroique.
      public static void main(String[] args) {
        ...   
        //var excalibur = new Sword("excalibur", 10);
        //var arthur = new Knight("arthur", excalibur);
        System.out.println(arthur.isHeroic());   // false
        arthur.setHeroicity(new Heroicity(2));   // applique un facteur 2
        System.out.println(arthur.isHeroic());   // true
        System.out.println(arthur.damage());     // 20
        System.out.println(arthur.protection()); // 0
        System.out.println(arthur);  // affiche: arthur damage: 20 protection: 0
                                     //            [excalibur]
      }
        

    Note: Une bonne façon de voir si votre design est correct est de remarquer que Heroicity ne doit pas avoir de getter.
  9. Finalement, on se rend compte qu'au lieu d'un seul bonus, il vaudrait mieux un bonus différent pour les dégâts et pour les protections. Modifier votre code pour qu'il marche avec l'ancien et le nouveau système de bonus d'héroicité.
    Faire en sorte que le code suivant affiche les bonnes valeurs.
      public static void main(String[] args) {
        ... 
        //var hydra = new Shield("hydra", 4);
        //var clarent = new Sword("clarent", 7);
        //var mordred = new Knight("mordred", hydra, clarent);
        mordred.setHeroicity(new Heroicity(2, 3)); // 2 sur les dégâts et 3 sur la protection
        System.out.println(mordred.isHeroic());    // true
        System.out.println(mordred.damage());      // 14
        System.out.println(mordred.protection());  // 12
        System.out.println(mordred);  // affiche: mordred damage: 14 protection: 12
                                      //            [hydra, clarent]