..

Le design pattern Observer

pourquoi les designs patterns

En français : “les patrons de conception”. Mais Design Pattern ça fait quand même plus sérieux. Un grand mot pour un principe très simple : lors de vos développements, sur un paradigme de programmation objet, vous allez tôt ou tard rencontrer un problème de conception. En gros, comment je structure mon code pour répondre à mon besoin.

La bonne nouvelle est que vous n’êtes pas le premier, ni le dernier, à vous posez ces questions et c’est là ou les designs patterns interviennent : ce ne sont jamais que des modèles déjà éprouvés pour répondre aux questions les plus fréquentes lors de conception objet. Les rédacteurs des designs patterns ont mis en commun les cas les plus classique de conception qu’ils ont pu rencontrer au cours de leur expérience et ils ont publié les solutions les plus “élégantes” qu’ils ont pu trouver.

Un design pattern n’est donc qu’un ensemble de solution sur étagère pour répondre aux principaux problèmes que vous aurez à rencontrer dans votre développement.

le pattern observer

Imaginons des modules “observateurs” en attente d’information d’un module “informateur”. Dès que le module informateur a une information, une modification à communiquer, il averti tous ses observateurs de son changement. Nous sommes dans une relation un à plusieurs : un objet doit communiquer à plusieurs objets.

Lors de la conception de ce pattern, une règle très importante doit être appliquée : “loose coupling”, que l’on peut traduire par “couplage lâche”. L’idée est que l’objet informateur ne connaisse que le minimum de chose de ses observateurs. Il sait juste qu’il a des observateurs, avec sans doute des propriétés et des méthodes complètements différentes mais qui partagent tous une fonction d’alerte.

Strategy Pattern

Et en réel, ça donne quoi ?

C’est très simple.

On commence par créer un contrat d’interface Subject avec 3 méthodes pour enregistrer un observateur, en supprimer et les avertir

package observer;

public interface Subject {
    void registerObserver(Observer o);
    void unRegisterObserver(Observer o);
    void notifyObservers();
}

Et maintenant on fait notre classe concrète avec un vrai “sujet” qui va enregistrer ses observateurs. Vous voyez qu’on enregistre les observateurs dans un tableau. On défini une propriété value qui sera la valeur qu’on voudra transmettre à nos observateurs

package observer;

import java.util.ArrayList;

public class ConcreteSubject implements Subject {

    private ArrayList<Observer> observers;
    private int value;

    public ConcreteSubject(){
        observers = new ArrayList<Observer>();
    }

    @Override
    public void registerObserver(Observer o) {
        observers.add(o);
    }

    @Override
    public void unRegisterObserver(Observer o) {
        observers.remove(o);
    }

    @Override
    public void notifyObservers() {
        for(Observer o : observers){
            o.update(this.value);
        }
    }

    public void changeValue(int i){
        this.value = i;
        this.notifyObservers();
    }
}

Ensuite, la même chose pour les observateurs

package observer;

public interface Observer {
    void update(int value);
}

Et nous allons créer 2 observateurs. Dans l’exemple, ils sont identiques mais l’intérêt est qu’ils peuvent être aussi différents que l’on veut. La seule contrainte est qu’ils implémentent l’interface Observer, et c’est … tout.

package observer;

public class ConcretObserver1 implements Observer {

    private ConcreteSubject concreteSubject;
    private int value = 0;

    public ConcretObserver1(ConcreteSubject concreteSubject){
        this.concreteSubject = concreteSubject;
        concreteSubject.registerObserver(this);
    }

    @Override
    public void update(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return this.value + "";
    }
}

package observer;

public class ConcretObserver2 implements Observer {

    private ConcreteSubject concreteSubject;
    private int value = 0;

    public ConcretObserver2(ConcreteSubject concreteSubject){
        this.concreteSubject = concreteSubject;
        concreteSubject.registerObserver(this);
    }

    @Override
    public void update(int value) {
        this.value = value;
    }

    @Override
    public String toString() {
        return this.value + "";
    }
}

Tout est maintenant en place. Nous pouvons exécuter notre code exemple. La valeur de nos observateurs est au départ de 0. Après changement de la valeur de notre sujet, tous ces observateurs prennent en compte maintenant la nouvelle valeur.

package observer;

public class Main {
    public static void main(String[] args) {
        ConcreteSubject concreteSubject = new ConcreteSubject();
        ConcretObserver1 concretObserver1 = new ConcretObserver1(concreteSubject);
        ConcretObserver2 concretObserver2 = new ConcretObserver2(concreteSubject);

        System.out.println(concretObserver1);
        System.out.println(concretObserver2);

        concreteSubject.changeValue(5);

        System.out.println(concretObserver1);
        System.out.println(concretObserver2);
    }
}

/*
output de ce code

0
0
5
5

*/