Design Pattern Observer

Description

Le design pattern observer a pour but de mettre en place un système de souscription entre 1 ou plusieurs objets. Cette souscription permet d'envoyer des notifications entre les objets reliés, comme des modifications apportées sur l'objet souscrit.

Nous aurons donc plusieurs objets :

✉️ Un sujet qui peut être observé par plusieurs objets et qui enverra les notifications aux objets qui l'observent. Il transmettra les informations émises aux différents observateurs. 🔭 Les observateurs qui vont observer un sujet et attendre des interactions, ils sont définis par une interface dite: Observer.

Mise en situation

💬 Nous créons une application de chat. On pourrait facilement se dire que l'on pourrait créer une boucle qui affiche les messages,... Vous l'aurez compris une catastrophe donc l'on ne veut pas entendre parler ! C'est ici que l'observer nous aider :

Nous avons un sujet qui sera un topic dans un chat auquel nous allons nous inscrire. Nous avons des observateurs qui seront des utilisateurs, qui recevront des messages et pourront en émettre. Ainsi nous avons un pattern beaucoup plus simple et propre !

Avantages et inconvénients

Avantages Inconvénients
✔️Faible couplage, car nous pouvons ajouter de nouveaux observateurs sans changer le sujet. En effet le sujet ne connait qu'une liste d'objet aligné sur l'interface observer et non les classes concrètes de cet observateur ❌Ordre aléatoire: les messages envoyés aux destinataires sont aléatoires étant donné que la liste d'observateurs peut changer.
✔️Contrôle: le sujet ne gère pas ses observateurs et cela est positif étant donné que chaque observateur peut lui-même gérer d'ignorer un message,... ❌Calculs inutiles dans le cas où nous avons beaucoup d'observateurs. Si ces observateurs ne gèrent pas les messages à ignorer, il y aura des calculs inutiles qui pourront mener à une énorme consommation.

Quand l'utiliser ?

💡 Vous avez des composants qui dépendent du status d'un autre objet. 💡 On le rencontre beaucoup dans les applications avec des interfaces graphiques où quand une donnée est modifiée, toutes les autres données sont modifiées à l'écran

Diagramme UML

Dans le cas de cet exemple, nous développerons un système de chat. Dans ce système, nous implémenterons uniquement la fonctionnalité qui permettra de recevoir les messages. Comme vous le savez, lorsque vous vous connectez à un chat vous avez la possibilité de vous inscrire à plusieurs topics. Dans ce cas, nous pouvons découper le système :

  • ✉️ Le sujet sera notre topic où les utilisateurs s'inscrivent.
  • 🔭 Les observateurs seront les utilisateurs qui se connectent au chat.

Dans ce cas, nous aurons une flexibilité dans notre système, qui permettra à un objet de se lier à un autre afin d'obtenir des informations.

Implémentation

Commençons par créer les bases de notre design package : l'interface Suject et l'interface Observer.

Subject

 package com.design.observer.base;

public interface Subject {
    public void register(Observer observer);

    public void unregister(Observer observer);

    public void notifyObservers();

    public Object getUpdate(Observer observer);
}

🔎 Register: permettra aux observateurs de s'enregistrer. 🔎 Unregister: permettra aux observateurs d'arrêter d'observer le topic. 🔎 NotifyObserver: permettra de notifier les observateurs d'un changement. 🔎 getUpdate: permettra de récupérer le changement notifié.

Observer

 package com.design.observer.base;

public interface Observer{
    public void update();

    public void setSubject(Observer observer);
}

🔎 Update: permettra aux observateurs de récupérer dans sa classe la mise à jour du sujet à travers de la fonction du sujet. 🔎 SetSubject: permettra aux observateurs d'enregistrer le topic.

Maintenant que nos interfaces sont créées, il nous faut ajouter la logique qui définira les méthodes. Dans le cas de notre chatTopic, nous devons implémenter les méthodes d'interface Subject. Pour vraiment comprendre le rôle de ce topic, nous allons détailler les interactions :

👤 Register permettra d'ajouter un participant à la liste des observateurs (uniquement s'il n'existe pas encore dans cette liste). ❌ UnRegister permettra à un utilisateur de se retirer de la liste des observateurs. 📩 sendNotify permettra d'informer les observateurs d'une modification, elle devra utiliser la méthode update des observateurs pour récupérer l'information. 🔄 getUpdate est un getter pour le message transmis. 📫 postMessage est un setter pour le message.

ChatTopic

 package com.design.observer.model;

import com.design.observer.base.Observer;
import com.design.observer.base.Subject;

import java.util.ArrayList;
import java.util.List;

public class ChatTopic implements Subject {

    private List<Observer> observers;
    private String message;

    public ChatTopic() {
        this.observers = new ArrayList<>();
    }

    @Override
    public void register(Observer observer) {
        if(observer == null) throw new NullPointerException("Null object/Observer");

        if(!this.observers.contains(observer)) {
            observers.add(observer);
        }
    }

    @Override
    public void unregister(Observer observer) {
        observers.remove(observer);
    }

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

    @Override    
    public Object getUpdate(Observer observer) {
        return this.message;
    }

    public void postMessage(String message) {
        System.out.println("Message posted to my topic : " + message);
        this.message = message;
        notifyObservers();
    }
}

ChatTopic est maintenant défini et répond parfaitement aux critères expliqués plus haut ! Maintenant, il nous reste à implémenter notre classe pour les observateurs. Dans cette classe :

🔄 Update: va récupérer la mise à jour du sujet et afficher un message par défaut dans le cas d'un message null. ✉️ SetSubject: va assigner le topic.

ChatTopicSubscriber

 package com.design.observer.model;

import com.design.observer.base.Observer;
import com.design.observer.base.Subject;

public class ChatTopicSubscriber implements Observer {

    private String name;

    //Reference to our subject class
    private Subject topic;

    public ChatTopicSubscriber(String name) {
        this.name = name;
    }

    @Override
    public void update() {
        String msg = (String)topic.getUpdate(this);
        if(msg == null)
            System.out.println(name + "No new message on this topic");
        else
            System.out.println(name + "Retrieving message: " + msg);
    }

    @Override
    public void setSubject(Subject subject) {
        this.topic = subject;
    }
}

Maintenant que nos observateurs et topic sont implémentés, nous allons tester cela directement dans la méthode main:

Main

 package com.design.observer;

import com.design.observer.base.Observer;
import com.design.observer.model.ChatTopic;
import com.design.observer.model.ChatTopicSubscriber;

publict class Main {
    public static void main(String[] args) {

        ChatTopic topic = new ChatTopic();

        //create observers
        Observer firstObserver = new ChatTopicSubscriber("FirstObserver");

        Observer secondObserver = new ChatTopicSubscriber("SecondObserver");

        Observer thirdObserver = new ChatTopicSubscriber("ThirdObserver");


        //Register them ...
        topic.register(firstObserver);
        topic.register(secondObserver);
        topic.register(thirdObserver);

        // Attaching observer to subject
        firstObserver.setSubject(topic);
        secondObserver.setSubject(topic);
        thirdObserver.setSubject(topic);


        //Check for updates
        firstObserver.update();
        thirdObserver.update();

        topic.postMessage("Hello Everybody");

        topic.unregister(firstObserver);

        topic.postMessage("Hello Everybody");

    }
}

Résultat :

 FirstObserver: No new message on this topic
ThirdObserver: No new message on this topic
Message posted to my topic: Hello Everybody
FirstObserver Retrieving message: Hello Everybody
SecondObserver Retrieving message: Hello Everybody
ThirdObserver Retrieving message: Hello Everybody
Message posted to my topic: Hello Everybody
SecondObserver Retrieving message: Hello Everybody
ThirdObserver Retrieving message: Hello Everybody

Nous avons donc fini notre implémentation et nous pouvons remarquer que :

  • Nous avons nos différents observateurs qui s'inscrivent sur un topic donné pour recevoir des messages.
  • En cas où nous voudrions d'autres topics, nous pourrons simplement créer une nouvelle classe.

Developpeur et architecte passionné, qui souhaite partagé son univers et ses découvertes afin de rendre les choses plus simple pour chacun