Skip to content
Snippets Groups Projects
README.md 9.09 KiB
Newer Older
# TP3 - Observateurs/Observable
anquetil's avatar
anquetil committed

Fabien Delecroix's avatar
Fabien Delecroix committed
Dans ce TP, vous allez vous familiariser avec le patron de conception observateurs/observable.
D'abord, en implémentant sa structure et son mécanisme.
Fabien Delecroix's avatar
Fabien Delecroix committed
Puis, en utilisant cette couche abstraite pour réaliser un petit programme qui suit la progression d'objectifs sur des saisies utilisateurs.
Fabien Delecroix's avatar
Fabien Delecroix committed
Pour commencer, cloner ce projet, éventuellement en faisant un fork préalable.
Si vous rencontrez des problèmes sur intelliJ au niveau des tests pour importer Observer ou Observable, supprimez les modules .iml.
anquetil's avatar
anquetil committed

Fabien Delecroix's avatar
Fabien Delecroix committed
## Partie 1 : Le patron Observateurs / Observable
La figure suivante vous présente le patron Observateurs (ici « Observer ») / Observable.
L’idée est qu’un ou plusieurs observateurs peuvent s’inscrire auprès d’un Observable et être avertis d’évènements qui se produisent sur ce dernier.
La méthode « update() » des Observateurs est déclenchée par l’Observable chaque fois qu’il veut prévenir d’un évènement.
anquetil's avatar
anquetil committed
Il utilise pour ça sa propre méthode « notifyObservers() ».

Thomas Clavier's avatar
Thomas Clavier committed
```mermaid
classDiagram
  direction LR
Thomas Clavier's avatar
Thomas Clavier committed
  
  class Observable
  <<Abstract>> Observable
  Observable  --> "*" Observer : #observers
  Observable ..> Observer : notifies
  Observable : +attach(Observer) void
  Observable : +detach(Observer) void
  Observable : #notifyObservers() void
  Observable : #notifyObservers(Object) void
Thomas Clavier's avatar
Thomas Clavier committed
  class Observer
  <<interface>> Observer
  Observer : +update(Observable) void
  Observer : +update(Observable, Object) void
Thomas Clavier's avatar
Thomas Clavier committed
```

Fabien Delecroix's avatar
Fabien Delecroix committed
Compléter la classe abstraite Observable de manière à pouvoir disposer du mécanisme Observateur/Observable.
Vérifiez la validité de votre implémentation à l'aide des tests fournis.
Fabien Delecroix's avatar
Fabien Delecroix committed
Lors du dernier test, une *ConcurrentModificationException* surviendra peut-être selon votre implémentation.
En effet, si l'observateur se désabonne pendant la boucle de notification, le risque de est modifier la structure sur laquelle on itère.
Pour remédier à ce problème, deux pistes s'offrent à vous :
- [trouver une autre collection](https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/Collection.html) qui ne posera pas cet inconvénient
- modifier le code d'Observable pour y pallier.

package fr.univlille.iut.r304.tp3.q1;

public abstract class Observable {
  public void attach(Observer obs) {
  }

  public void detach(Observer obs) {
  }

  protected void notifyObservers() {
  }

  protected void notifyObservers(Object data) {
  }
Fabien Delecroix's avatar
Fabien Delecroix committed
## Partie 2 : Application
Fabien Delecroix's avatar
Fabien Delecroix committed
On souhaite implémenter un premier suivi d'objectif simple sur un texte, qui se met à jour à chaque nouvelle ligne saisie sur l'entrée standard.
Nous utiliserons le patron observateurs/observé pour l'implémenter.

On va donc disposer d'une classe Texte, encapsulant un Scanner pour traiter un flux, ici l'enrée standard :
```mermaid
classDiagram
  
  class Texte
  Texte : +Texte(InputStream)
  Texte : +lancer() void

  Texte  --> "1" Scanner : #scan

```
Fabien Delecroix's avatar
Fabien Delecroix committed
ainsi que d'une classe Objectif, chargée d'afficher au fur et à mesure la progression sur l'objectif donné, ici simplement en terme de lignes à saisir :
```mermaid
classDiagram
Fabien Delecroix's avatar
Fabien Delecroix committed
  class Objectif {  
Fabien Delecroix's avatar
Fabien Delecroix committed
      #nbRestant : int
      #nom : String
Fabien Delecroix's avatar
Fabien Delecroix committed
Implémentez ces classes dans un sous-paquetage *q2*.

On devra par exemple pouvoir dérouler le scénario suivant (pour y parvenir quelques questions ci-dessous pour vous guider) :
Fabien Delecroix's avatar
Fabien Delecroix committed
<pre>
>>>Objectif 1 - nombre de lignes : encore 3<<<
<b>Hello</b>
>>>Objectif 1 - nombre de lignes : encore 2<<<
<b>The answer is 42</b>
>>>Objectif 1 - nombre de lignes : encore 1<<<
<b>Sayonara</b>
>>>Objectif 1 - nombre de lignes atteint<<<
<b>Mission accomplie</b>
<b>!fin</b>
Fin de la saisie.
</pre>
*En gras, les saisies utilisateurs, les autres chaînes sont affichées par le programme*
*La lecture de texte prend fin lorsqu'on rencontre la chaîne "!fin"*
Fabien Delecroix's avatar
Fabien Delecroix committed

* Qu'est-ce qui est observable ici ? \
Faîtes hériter d' *Observable* la classe correspondante.
* Quel sera l'observateur ? \
Faîtes implémenter *Observer* à la classe correpsondante.
* En mode *push*, quelle *data* va-t-on récupérer de l'*Observable* qu'on suit lorsqu'il appellera notre méthode *update(Observable o, Object data)* ? \
Faîtes en sorte qu'elle soit bien envoyée par l'observable.
* Dans un *Main*,  instancier les différentes classes nécessaires à obtenir le scénario ci-dessus en pensant bien à gérer l'abonnement et le désabonnement de l'observateur à l'observable.
Fabien Delecroix's avatar
Fabien Delecroix committed

Fabien Delecroix's avatar
Fabien Delecroix committed
Écrivez des tests qui démontrent le bon fonctionnement de votre implémentation.
Pour ce faire, on va bien sûr éviter d'aller lire sur l'entrée standard...
À la place, on pourra donner un flux d'entrée vers une chaîne de caractères, par exemple
```java
String text = "abc\ndef\n";
Fabien Delecroix's avatar
Fabien Delecroix committed
InputStream is = new ByteArrayInputStream(text.getBytes());
Fabien Delecroix's avatar
Fabien Delecroix committed
```

### Autres objectifs
Fabien Delecroix's avatar
Fabien Delecroix committed
On va désormais ajouter d'autres objectifs, sur les mêmes saisies, mais vérifiant un critère plus fin, qu'un caractère donné ait été saisi un certain nombre de fois.
Créer une classe pour ce faire, de manière à mutualiser le plus possible le code avec **Objectif**, en n'hésitant pas à refactoriser au besoin.
Là encore, vous réaliserez un Main et des tests.
Voici un exemple de comportement attendu :
Fabien Delecroix's avatar
Fabien Delecroix committed

Fabien Delecroix's avatar
Fabien Delecroix committed
<pre>
>>>Objectif 1 - nombre de lignes : encore 3<<<
nombre de e : encore 5
Fabien Delecroix's avatar
Fabien Delecroix committed
<b>Hello</b>
>>>Objectif 1 - nombre de lignes : encore 2<<<
>>>Objectif 2 - nombre de e : encore 4<<<
<b>The answer is 42</b>
>>>Objectif 1 - nombre de lignes : encore 1<<<
>>>Objectif 2 - nombre de e : encore 2<<<
<b>Sayonara</b>
>>>Objectif 1 - nombre de lignes atteint<<<
>>>Objectif 2 - nombre de e : encore 2<<<
<b>The end</b>
>>>Objectif 2 - nombre de e atteint<<<
<b>Rideau</b>
<b>!fin</b>
Fin de la saisie.
</pre>

Construisez 1 ou 2 autres objectifs sur d'autres critères, par exemple avec un nombre de mots d'une taille donnée ou un nombre de majuscules ou chiffres à atteindre.
Fabien Delecroix's avatar
Fabien Delecroix committed
Quels sont les avantages de se baser sur le patron Observer/Observable ?
Fabien Delecroix's avatar
Fabien Delecroix committed

## Partie 3 : Propriétés observables
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
JavaFX défini la notion de propriété observable (javafx.beans.value. ObservableValue) avec un mécanisme qui permet à un observateur d’être averti de tout changement d’état (i.e. de valeur) de la propriété (javafx.beans. property.Property).
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
À ceci s’ajoute les connexions qui permettent à des propriétés de s’observer mutuellement et que l’une réagisse automatiquement aux changement d’état (i.e. de valeur) de l’autre.
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
Il y a deux types de connexion entre les propriétés : mono ou bi-directionnelle.
La monodirectionnelle propage les changement d’une propriété (« source ») vers l’autre (« destination »), la bidirectionnelle propage les changements de n’importe laquelle des deux propriétés vers l’autre.
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
Ce sont des mécanismes pratiques mais qui obligent à importer JavaFX ce qui n’est pas toujours souhaitable dans un projet.
Vous devez réimplémenter ces mécanismes des propriétés.
Nicolas Anquetil's avatar
Nicolas Anquetil committed

Fabien Delecroix's avatar
Fabien Delecroix committed
Rendez la classe ObservableProperty observable.
Outre le fait qu'elle soit observable (attach, detach), cette classe doit avoir une propriété à laquelle on peut accéder :
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
```java
package fr.univlille.iut.r304.tp3.q3;
Fabien Delecroix's avatar
Fabien Delecroix committed

Fabien Delecroix's avatar
Fabien Delecroix committed
public class ObservableProperty {
Fabien Delecroix's avatar
Fabien Delecroix committed

Fabien Delecroix's avatar
Fabien Delecroix committed
    public Object getValue() {
        
    }
Fabien Delecroix's avatar
Fabien Delecroix committed

Fabien Delecroix's avatar
Fabien Delecroix committed
    public void setValue(Object val) {
        
    }
anquetil's avatar
anquetil committed
  ...
Fabien Delecroix's avatar
Fabien Delecroix committed
}
anquetil's avatar
anquetil committed
```
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
Bien sûr, le fait de changer la valeur de la propriété doit être notifié aux observateurs de celle-ci.
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
Notez que par simplicité, on utilisera une propriété de type `Object` (donc n'importe quel type).
Fabien Delecroix's avatar
Fabien Delecroix committed
On aurait aussi pu définir une classe générique avec un type paramétrique pour typer cette propriété.
Nicolas Anquetil's avatar
Nicolas Anquetil committed

Vérifier la validité de votre implémentation en exécutant les tests associés à cette question.

Fabien Delecroix's avatar
Fabien Delecroix committed
Implémentez la classe ConnectableProperty pour qu'elle puisse se connecter de façon mono ou bi-directionnelle (elle aura donc à la fois le rôle d'observateur et d'observable) :
Nicolas Anquetil's avatar
Nicolas Anquetil committed

anquetil's avatar
anquetil committed
```java
package fr.univlille.iut.r304.tp3.q3;
Nicolas Anquetil's avatar
Nicolas Anquetil committed

Fabien Delecroix's avatar
Fabien Delecroix committed
public class ConnectableProperty extends ObservableProperty ... {
Fabien Delecroix's avatar
Fabien Delecroix committed

public void connectTo(ConnectableProperty other) {
}

public void biconnectTo(ConnectableProperty other) {
}

public void unconnectFrom(ConnectableProperty other) {
}
anquetil's avatar
anquetil committed
  ...
Fabien Delecroix's avatar
Fabien Delecroix committed
## Partie 4 : Application avec la conversion de devises

On souhaite réaliser une application de portefeuille gérant la conversion dans différentes monnaies.
Dans un premier temps, on va disposer d'un montant en euros et souhaiter l'afficher aussi en dollars.

Dans un sous-paquetage *q4*, implémentez une classe **Devise** héritant de *ConnectableProperty* de manière à pouvoir instancier deux devises différentes, l'une correspondant aux euros, l'autre aux dollars.
Les valeurs n'étant pas les mêmes dans les deux monnaies, la méthode *void update(Subject other, Object data)* va être à redéfinir de manière à opérer la conversion.

Une fois la classe implémentée, réalisez un *Main* avec 2 devises synchronisées, l'ajoutant ou le retrait d'un montant dans l'une se répercutant automatiquement sur l'autre.

Voici un exemple de scénario :
```
Ajout 20$
18,00 €
20,00 $
Retrait 11€
7,00 €
7,78 $
```

Ajoutez une troisième devise et démontrez la validité de votre solution à l'aide de tests.