Contrôle continu 2024-25

1. Refactorings

1.1. Définitions

Expliquez la différence entre une opération de refactoring et l'activité de refactoring

Solution

Une opération de refactoring est une transformation de programme source, qui préserve son comportement visible.

L’activité de refactoring consisté à améliorer la conception du code source d’un logiciel, sans modifier son comportement visible, grâce à l’application d’opération de refactorings.

1.2. Spécialisation d’un champ

Expliquez le fonctionnement de l’opération de refactoring "Spécialisation d’un champ" (Push Down Field)

Solution

Cette opération déplace un champ d’une super-classe mère vers toutes ses sous-classes.

1.3. Préconditions

Quelles sont les pré-conditions nécessaires pour l’application de l’opération de refactoring "Spécialisation d’un champ" (Push Down Field), dans le contexte d’un programme Java?

Solution
  1. le champ n’est pas privé

  2. soit C la classe contenant le champ (la super-classe):

    1. le champ n’est pas référencé par les méthodes de C

    2. le champ n’est pas référencée sur les instances de C

2. Classes immuables

Dans cet exercice, vous allez implémenter, en Java, une collection générique, contenant seulement 3 méthodes : ajouter un élément, supprimer un élément et calculer la taille de la collection.

Cette classe doit répondre à deux exigences :

  1. Elle doit être immuable, ses instances ne sont pas modifiables.

  2. Elle ne doit pas accepter les valeurs nulles.

2.1. Signature

Commencez par spécifier seulement la signature des 3 méthodes.

Solution
  • Les méthodes add() et remove() ne doivent pas retourner void

ImmutableCollection<T> add(T element);
ImmutableCollection<T> remove(T element);
int size();
}

2.2. La classe ImmutableCollection

Donnez maintenant le code source de toute la classe, à l’exclusion des 3 méthodes, que vous spécifierez dans la suite de l’exercice.

Solution
  • Les classes immuables doivent être déclarées final

  • Ici, la solution utilise un tableau (T[]), mais l’utilisation des classes de la JFC était possible

  • La méthode contains() n’est pas obligatoire

public final class ImmutableCollection<T> {
    private final T[] elements;

    public ImmutableCollection() {
        elements = (T[]) new Object[0];
    }

    private ImmutableCollection(T[] elements) {
        this.elements = elements;
    }

    private boolean contains(T element) {
        for (int i = 0; i < elements.length; i++) {
            if (elements[i].equals(element)) {
                return true;
            }
        }
        return false;
    }
}

2.3. Ajout d’un élément

Donnez le code source de la méthode d’ajout d’un élément.

Solution
public ImmutableCollection<T> add(@Nonnull T element) {
    Preconditions.checkNotNull(element);
    if (this.contains(element)) {return this;}

    Object[] newElements = new Object[elements.length + 1];
    System.arraycopy(elements, 0, newElements, 0, elements.length);
    newElements[elements.length] = element;
    return new ImmutableCollection<>((T[]) newElements);
}

2.4. Suppression d’un élément

Donnez le code source de la méthode de suppression d’un élément.

Solution
public ImmutableCollection<T> remove(T element) {
    Preconditions.checkNotNull(element);
    if (!this.contains(element)) {return this;}

    Object[] newElements = new Object[elements.length - 1];
    int j = 0;
    for (int i = 0; i < elements.length - 1; i++) {
        if (!elements[i].equals(element)) {
            newElements[j++] = elements[i];
        }
    }
    return new ImmutableCollection<>((T[]) newElements);
}

2.5. La taille de la collection

Enfin, donnez le code source de la méthode qui retourne la taille de la collection.

Solution
public int size() {
    return elements.length;
}

3. Association Bidirectionnelle

On souhaite implémenter une association bidirectionnelle entre les classes Task et Event. Une instance de la classe Task peut être liée à au plus une instance de la classe Event et une instance de la classe Event peut être liée à au plus une instance de la classe Task.

Ces deux classes ont été codées partiellement en Java, comme suit:

public class Task {
    private Optional<Event> event = Optional.empty();

    public Optional<Event> getEvent() {
        return this.event;
    }

    public void setEvent(Event event) {
        this.unsetEvent();
        event.unsetTask();
        event.basicSetTask(this);
        this.basicSetEvent(event);
    }

    public void basicUnsetEvent() {
        this.event = Optional.empty();
    }

    public void basicSetEvent(Event event) {
        this.event = Optional.ofNullable(event);
    }

    public void unsetEvent() {
        event.ifPresent(e -> e.basicUnsetTask());
        this.basicUnsetEvent();
    }
}
public class Event {
    private Optional<Task> task = Optional.empty();

    public Optional<Task> getTask() {
        return task;
    }

    public void setTask(Task task) {
    }

    public void unsetTask() {
        task.ifPresent(t -> t.basicUnsetEvent());
        this.basicUnsetTask();
    }

    public void basicSetTask(Task task) {
        this.task = Optional.of(task);
    }

    public void basicUnsetTask() {
        this.task = Optional.empty();
    }
}

3.1. La méthode setTask()

Implémentez la méthode Event::setTask(), de manière à assurer l’intégrité référentielle

Solution
    public void setTask(Task task) {
        this.unsetTask();
        task.unsetEvent();
        task.basicSetEvent(this);
        this.basicSetTask(task);
    }

4. Le patrons de conception Singleton

4.1. Définition

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

Solution
  • L’objectif de ce patron est d’assurer qu’une classe n’a qu’une seule instance et de fournir un point d’accès global à cette instance.

  • L’objectif du patron singleton est d’assurer qu’une classe n’a qu’une seule instance, de fournir un point d’accès global à cette instance et de permettre sont instanciation 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

4.2. Mise en oeuvre

Implémentez, en Java, une classe appelée System, en assurant que cette classe ne peut avoir qu’une seule instance, grâce au patron Singleton

Solution
public class System {
    private System() {}

    public static System getInstance() {
        return Holder.instance;
    }

    private static class Holder {
        public static final System instance = new System();
    }
}