Contrôle continu 2023-24

1. Techniques de programmation

La classe ArrayList, présentée ci-dessous, combine deux techniques de programmation différentes~: défensive et assertive. Basez-vous sur cet exemple pour répondre aux questions suivantes.

public class ArrayList<E> {
    private E[] data;
    private int size = 0;
    public ArrayList(int initialSize) {
        data = (E[]) (new Object[initialSize]);
    }
    public E get(int index) {
        if (index < 0 || index >= size) throw new IndexOutOfBoundsException();

        return resolve(index);
    }
    private E resolve(int index) {
        assert index < size && index >= 0;
        assert data != null;

        return data[index];
    }
    public void add(@Nonnull E element) {
        ensureCapacity();
        data[size++] = element;
    }
    private void ensureCapacity() {
        if (size == data.length) {
            E[] oldData = data;
            data = (E[]) (new Object[size * 2]);
            System.arraycopy(oldData, 0, data, 0, size);
        }
    }
}

1.1. Programmation défensive

Expliquez les principes de la programmation défensive

Solution

La programmation défensive est une technique de développement de code maintenable.

  • Elle garantit que le code se comporte de manière correcte, en dépit d’une entrée incorrecte.

  • Elle garantit qu’une méthode ne peut être exécutée que si certaines conditions sont remplies.

1.2. Gardes

A votre avis, quel était l’objectif du développeur lorsqu’il a ajouté une garde à la méthode get() ?

Solution

L’objectif du développeur est d’empêcher que le tableau soit accédé avec un index invalide (inférieur à zéro ou égal ou supérieur à la taille du tableau)

1.3. Programmation assertive

Expliquez les principes de la programmation assertive.

Solution
  • C’est une technique qui suit le principe de l’échec rapide et visible.

  • Elle empêche de propagation d’erreurs dans le code : l’exécution s’arrête à l’endroit de l’erreur.

  • Elle empêche le système de rentrer dans un état incohérent en raison des éventuels changements dans le code source.

1.4. Assertions

A votre avis, quel était l’objectif du développeur lorsqu’il a ajouté deux assertions à la méthode resolve() ?

Solution
  • Le développeur utilise ces assertions pour deux raisons:

    1. Il veut s’assurer que si cette classe est modifiée, les appelants de la méthode resolve() continueront à respecter ses pré-conditions, c’est dire, que l’index est valable et que le tableau a bien été initialisé.

    2. Il veut rendre plus lisible la méthode resolve(), les assertions expliquent ce que cette méthode attend avant d’être appelée.

  • Notez que le code actuel respecte bien les pré-conditions de la méthode resolve(): les assertions empêchent l’impossible.

1.5. Annotations

Le paramètre element de la méthode `add() utilise l'annotation `@Nonnull. Quelle est l’utilité de cette annotation ? Quel est son impact sur l’exécution de la méthode ?

Solution
  • Cette annotation permet aux outils d’analyse statique de code et à certains IDEs de vérifier que la méthode add() n’est pas appelée avec des arguments nuls.

  • L’annotation n’a aucun impact sur l’exécution.

2. Patrons de conception

2.1. Poids mouche

Dans quel contexte il est conseillé d’utiliser le patron "Poids plume" (Flyweight) ? Donnez des exemples de son utilisation.

Solution
  • On utilise le patron poids mouche lors qu’on a besoin d’instancier beaucoup de petits objets et que l’on souhaite réduire l’empreinte mémoire.

  • La classe java.lang.Integer utilise ce patron pour limiter le nombre d’instances.

2.2. Singleton

Quel est l’objectif principal du patron de conception "Singleton" ? Dans quels cas est-il approprié de l’utiliser ?

Solution
  • L’objectif du patron singleton est d’assurer qu’une classe n’a qu’une seule instance et fournir un point d’accès global à cette instance et aussi lorsqu’on souhaite l’instancier de manière paresseuse.

  • On l’utilise lorsqu’il ne doit y avoir qu’une seule instance d’une classe, et elle doit être accessible aux clients à partir d’un point d’accès bien connu et lorsque cette unique instance doit être extensible par sous-classe et que les clients doivent être en mesure d’utiliser une instance d’une sous-classe sans modifier leur code

2.3. Décorateur

Considérez la classe Character, qui représente un personnage de jeu de rôles et dont le code source Java est le suivant :

public class Character {
    private String name;
    private int    life;
    private Point  position;
    private int    currentHP;
    private int    intellect;
    private int    strength;

    public int attack() {
        return strength * life/100;
    }

    public int defend() {
        return intellect * life/100;
    }

    public void receiveHit(int intensity){
        life = life - intensity;
    }

    public boolean isAlive() {
        return life > 0;
    }

    public int getLife() {
        return life;
    }

    public String getName() {
        return name;
    }
}

On souhaite faire évoluer cette mise en œuvre pour permettre la gestion d’équipements, comme un bouclier ou une épée, qui modifient le comportement d’un personnage. Pour ce faire, nous allons utiliser le patron de conception "Décorateur".

Proposez une solution (une instance du patron Décorateur) permettant la gestion de deux équipements : les boucliers et les épées. Le bouclier doit multiplier par 3 la force de défense, c’est-à-dire, le résultat de la méthode defend(). L’épée doit multiplier par 5 la force d’attaque, autrement dit, le résultat de la méthode attack(). Le bouclier et l’épée peuvent être utilisés par n’importe quel personnage.

Solution
  1. Le patron décorateur a besoin d’une classe abstraite ou une interface, les décorateurs ne peuvent pas être des sous-classes de la classe Character.

  2. Première étape, ajout d’une interface appelée ICharacter (le nom n’est pas important), la classe Character doit implémenter cette interface.

    package fr.unantes.sce.patterns.decorator;
    
    public interface Character {
        int attack();
        int defend();
        void receiveHit(int intensity);
        boolean isAlive();
        int getLife();
        String getName();
    }
  3. Deuxième étape, ajout d’un décorateur abstrait. Cette classe n’est pas obligatoire, mais elle permet de réduire le nombre de lignes de code.

    package fr.unantes.sce.patterns.decorator;
    
    public abstract class AbstractDecorator implements Character {
        protected Character character;
    
        public AbstractDecorator(Character character) {
            this.character = character;
        }
    
        @Override
        public int attack() {
            return character.attack();
        }
    
        @Override
        public int defend() {
            return character.defend();
        }
    
        @Override
        public void receiveHit(int intensity) {
            character.receiveHit(intensity);
        }
    
        @Override
        public boolean isAlive() {
            return character.isAlive();
        }
    
        @Override
        public int getLife() {
            return character.getLife();
        }
    
        @Override
        public String getName() {
            return character.getName();
        }
    }
  4. Troisième étape, le décorateur bouclier.

    package fr.unantes.sce.patterns.decorator;
    
    public class ShieldDecorator extends AbstractDecorator implements Character {
    
        public ShieldDecorator(Character character) {
            super(character);
        }
    
        @Override
        public int defend() {
            return character.defend() * 3;
        }
    }
  5. Dernière étape, le décorateur épée.

    package fr.unantes.sce.patterns.decorator;
    
    public class SwordDecorator extends AbstractDecorator implements Character {
    
        public SwordDecorator(Character character) {
            super(character);
        }
    
        @Override
        public int attack() {
            return character.attack() * 5;
        }
    }

3. Traduction de modèles de conception en code Java

Considérez le diagramme de classes UML présenté ci-dessous.

svg

Que représente ce diagramme ? Expliquez le rôle de tous les éléments qui le composent.

Solution
  • Le diagramme représente deux classes, Personnage et Kart

  • La classe Personnage possède un attribut, nom, de type String

  • La classe Kart possède, elle-aussi, un seul attribut, modèle, de type String

  • Ces deux classes sont reliées par une association bidirectionnelle, sans nom

  • Cette association possède deux rôles, pilote et karts

  • Le rôle pilote la relie à la classe Personnage et possède une multiplicité de valeur [0..1].

4. Diagramme d’objets

Dessinez un diagramme d’objets UML représentant l'état final des instances du diagramme de classes précédent, après l’exécution des opérations énumérées ci-dessous :

  1. Création de l’instance Yoshi de la classe Personnage

  2. Création de l’instance Mario de la classe Personnage

  3. Création de l’instance Carrera Quad de la classe Kart

  4. Création de l’instance City Tripper de la classe Kart

  5. Ajout de City Tripper aux karts de Yoshi

  6. Affectation de Mario en tant que pilote de Carrera Quad

Solution
  1. La notation des diagrammes objet est disponible ici : https://www.uml-diagrams.org/class-diagrams-overview.html#object-diagram

  2. Création des instances

    svg
  3. Ajout de City Tripper aux karts de Yoshi

    svg
  4. Notation alternative (liens unidirectionnels)

    svg
  5. Affectation de Mario en tant que pilote de Carrera Quad

    svg
  6. Notation alternative (liens unidirectionnels)

    svg