Voir le flux RSS

Gugelhupf

[Tutoriel] Le design pattern Observer

Noter ce billet
par , 26/10/2016 à 00h17 (3207 Affichages)
Auteur : Gokan EKINCI
Date de 1ère publication : 2016-10-26
Date de mise à jour : 2016-10-26
Licence : CC BY-NC-SA


L'objectif de cet article sera de vous présenter le design pattern Observer.

Le design pattern Observer permet de notifier des objets Observer lorsqu’un objet Observable est modifié.

L’objet A qui contient la donnée qui sera régulièrement modifiée implémente Observable :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
public interface Observable<T> {
    public void addObserver(Observer<T> observer);
    public void notifyObservers(T data);
}

L’objet B qui sera notifié par la modification de l’objet A implémente Observer :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
public interface Observer<T> {
    public void update(T data);
}

Cas d’utilisation : Nous avons une classe A « Worker » qui va régulièrement mettre à jour une valeur de type T (un Integer par exemple) dans une méthode work() :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.util.*;
 
public class Worker implements Observable<Integer> {
    private List<Observer<Integer>> observers = new ArrayList<>();
 
    @Override
    public void addObserver(Observer<Integer> observer) {
        observers.add(observer);
    }
 
    @Override
    public void notifyObservers(Integer data) {
        for (Observer<Integer> observer : observers) {
            observer.update(data);
        }
    }
 
    public void work() {
        for (int i = 0; i < 10; i++) {
            notifyObservers(i);
        }
    }
}

Nous avons deux classes B « Screen » qui seront notifiés par la modification du Worker, l’un des écrans va écrire la donnée sur la sortie standard « out », et l’autre va écrire la donnée sur la sortie d’erreur « err » :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
interface Screen extends Observer<Integer> {
    public void update(Integer data);
}
 
class ScreenOut implements Screen {
    @Override
    public void update(Integer data) {
        System.out.println(data);
    }
}
 
class ScreenErr implements Screen {
    @Override
    public void update(Integer data) {
        System.err.println(data);
    }
}


Exécutons notre code :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Main {
    public static void main(String[] args) {
        // On initialise nos observers :
        Screen screenOut = new ScreenOut();
        Screen screenErr = new ScreenErr();
 
        // On initialise notre observable :
        Worker worker = new Worker();
        worker.addObserver(screenOut);
        worker.addObserver(screenErr);
        worker.work();
    }
}

Analyse : Le code affiche les chiffres 0 à 9 dans la sortie standard « out » et « err ».



Pour les plus curieux : Le langage Java intègre le pattern Observer depuis la version 1.0 du JDK, reprenons les exemples précédents avec les classes standards Observable et Observer.

Soit la classe Worker :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
import java.util.Observable;
 
public class Worker extends Observable {
 
    public void work() {
        for (int i = 0; i < 10; i++) {
            setChanged();
            notifyObservers(i);
        }
    }
}


Soit les classes screens :
Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import java.util.Observer;
 
interface Screen extends Observer {
    public void update(Observable o, Object data);
}
 
class ScreenOut implements Screen {
    @Override
    public void update(Observable o, Object data) {
        System.out.println(data);
    }
}
 
class ScreenErr implements Screen {
    @Override
    public void update(Observable o, Object data) {
        System.err.println(data);
    }
}

La classe pour exécuter notre code (cf : Main) ne change pas. Le résultat avec l'exemple précédent est bien sûr le même.


Conclusion : Les classes du pattern Observer inclus dans le JDK permettent d’avoir un code un poil plus court pour notre classe Worker, cependant elle a non seulement l’inconvénient d’être une classe que l’on doit hériter (ce n’est pas très pratique car l’héritage multiple n’existe pas en Java et par conséquent la classe Workerne peut pas hériter d’une autre classe), mais en plus nous sommes forcés de caster explicitement le paramètre Object de la méthode update() dans notre Observer.

Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Viadeo Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Twitter Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Google Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Facebook Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Digg Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Delicious Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog MySpace Envoyer le billet « [Tutoriel] Le design pattern Observer » dans le blog Yahoo

Mis à jour 26/10/2016 à 20h54 par Gugelhupf (Ajout du tag [Tutoriel] au titre)

Catégories
Java , Programmation

Commentaires

  1. Avatar de hwoarang
    • |
    • permalink
    Bonjour,

    Le tuto me parait clair et efficace.

    Par contre, je trouve que declarer "public void notifyObservers(T data);" dans l'Observable est une erreur parce qu'elle rend publique une fonction interne de la classe (c'est elle et elle seule qui sait quand elle doit notifier les observers). Et, meme si je suis d'accord avec toi pour dire qu'une fonction pour notifier les observers est appropriée, à l'implémentation, je ne vois pas trop pourquoi on l'obligerait.

    Dans tous les cas, merci pour le tuto, ca aidera les debutants à comprendre ce pattern ^^
  2. Avatar de Gugelhupf
    • |
    • permalink
    Bonsoir hwoarang, et merci

    Mon Observable est une interface et une interface ne peut avoir que des méthodes de visibilité public, même si on ne précise pas le mot-clé public, on n'a pas de visibilité private-package, mais public pour une interface. D'ailleurs bien que le Observable du JDK soit une classe, sa méthode notifyObservers() est elle aussi public.
  3. Avatar de hwoarang
    • |
    • permalink
    J'avais bien remarqué que c'était une interface
    A mon avis, ce n'est pas une bonne chose de mettre dans l'interface cette methode pour les raisons que j'ai donné. De maniere générale, je pense qu'il est préférable de ne laisser public que ce qui doit l'etre (et donc, qui doit etre utilisé par une autre classe à un moment ou un autre - ce qui n'est pas le cas de la fonction qui notifie les observers).

    Mais bon, ce n'est que mon avis, chacun est libre de faire comme il veut ^^
    Et sur le fond, ca ne change rien à la description du pattern.
  4. Avatar de bouye
    • |
    • permalink
    Il serait difficile de faire autrement en Java justement car le contenu des interfaces est public. Il reste possible de le faire avec une classe abstraite mais alors on retombe sur le fait qu'il n'est pas possible d'avoir un héritage multiple.

    À noter que, oui, ces classes sont là depuis le JDK 1.0 mais qu'elles sont mises de coté dès le JDK 1.1 avec l'ajout des listeners et des events qui sont une autre manière d'aborder le pattern observer (pas réservée a AWT/Swing/GUI contrairement à ce que la plupart des gens peuvent croire).
  5. Avatar de hwoarang
    • |
    • permalink
    Citation Envoyé par bouye
    Il serait difficile de faire autrement en Java justement car le contenu des interfaces est public
    Ce que je proposais, c'etait juste de ne pas mettre la fonction de notification dans l'interface. Apres tout, à l'implementation, je ne vois pas pourquoi je serais obligé d'utiliser une fonction (alors que je pourrais m'amuser à duppliquer une boucle foreach ). Plus sérieusement, c'est surtout exposer la fonction de notification qui me pique un peu les yeux ^^
  6. Avatar de bouye
    • |
    • permalink
    Je pense surtout que tu ne comprends que définir cela dans une interface permet de varier l’implémentation. Après il n'y a pas 36 millions de façon de faire pour invoquer une méthode d'une autre classe : soit elle est publique soit elle est package protected. Et comme justement on parle d'une interface ça élimine d'office le package protected.

    Code Java : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    public interface ZeObserver {
         public void coucou(Result result);
    }

    Maintenant, ceci dit, on est pas obligé non-plus d’étendre l'interface dans l'objet observateur, il existe d'autres manières de faire qui permettent de cacher la chose et ainsi permettent d’éviter qu'une tierce-partie puisse invoquer cette méthode :
  7. Avatar de hwoarang
    • |
    • permalink
    Citation Envoyé par bouye
    Je pense surtout que tu ne comprends que définir cela dans une interface permet de varier l’implémentation. Après il n'y a pas 36 millions de façon de faire pour invoquer une méthode d'une autre classe : soit elle est publique soit elle est package protected. Et comme justement on parle d'une interface ça élimine d'office le package protected.
    sisi, je comprends bien. Et, justement pour la raison que tu donnes toi meme, à mon avis, la methode de notification ne devrait pas etre dans l'interface.
    Pour s'en convaincre, il suffit d'imaginer ce que peuvent etre amenées à faire des fonctions/objets qui manipuleraient des Observables. De maniere assez evidente, ils vont vouloir s'enregistrer (donc la fonction addObserver est, évidemment, obligatoire). Par contre, ils ne voudront pas forcer une notification (appeler notifyObservers). Pire, comme cette fonction est dans l'interface, elle est public et peut donc etre appelée. C'est ca qui me derange. D'ailleurs, tu remarqueras que dans ton propre exemple, tu n'appelles pas la fonction de notification

    Encore une fois, à l'implementation, utiliser une fonction parait etre un choix evident. Par contre, à mon sens, il ne devrait pas etre dans l'interface.

    Sinon, par rapport aux moyens que tu donnes de corriger ce probleme, je suis d'accord avec toi mais je pense que c'est encore mieux de ne pas mettre cette contrainte quand on refait le pattern (si on utilise l'Observable du JDK, ce que tu proposes est necessaire mais tant qu'à refaire le pattern, autant ne pas remettre la fonction de notification).