Le modèle observateur(Observer Pattern)
Le pattern observer selon l'ouvrage "Design Patterns: Elements of Reusable Object-Oriented Software" du GOF.
Alias
Intention de l'observer pattern
Définit une interdépendence de type un à plusieurs de telle manière que, quand un objet change d'état, tous ceux qui en dépendent sont notifiés et mis à jour automatiquement.
Motivation de l'observer pattern
Un effet commun du partitionnement d'un système en une collection de classes coopérantes, est l'obligation de maintenir la cohérence (consistance) des objets en relation. Il n'est pas souhaitable d'obtenir cette cohérence au prix d'un couplage trop étroit entre les classes, car cela réduirait leur réutilisabilité.
Par exemple, de nombreux outils d'interface graphique utilisateurs séparent les aspects de présentation de l'interface utilisateur, des données sous-jacentes de l'application. Les classes qui définissent les données de l'application et les présentations peuvent être réutilisées indépendamment. Elles peuvent également travailler ensemble. Autant un objet tableur (spreedsheet) qu'un objet diagramme a barres (bar chart), peuvent afficher l'information du même objet donnée de l'application, avec des représentations différentes. Le tableur et le diagramme ignorent chacun l'existence de l'autre, nous laissant utiliser celui que nous désirons. Mais ils se comportent comme s'ils se connaissaient. Quand l'utilisateur modifie l'information dans le tableur, la représentation en diagramme est immédiatement modifiée.
Ce comportement implique que le tableur et le diagramme dépendent de l'objet donnée, et qu'ils doivent être notifiés de toute modification de l'état de cet objet. Et il n'y a aucune raison de limiter à 2 le nombre d'objets dépendants; il peut y avoir n'importe quel nombre d'interfaces utilisateur différentes pour une même donnée.
L'Observer pattern décrit comment établir ces relations. Les objets de base de ce modèle sont le sujet et l'observateur. Chaque sujet peut avoir un nombre variable d'observateurs dépendants. Tous les observateurs sont prévenus (opération notify) chaque fois que le sujet subit une modification de son état. En réponse, chaque observateur demandera au sujet son état afin de sincroniser le sien.
Ce genre d'interaction est aussi connu sous le nom publish-subscribe (diffusion-souscription). Le sujet diffuse les notifications. Il les expédie à ses observateurs sans en connaitre le type réel. Un nombre indéfini d'observateurs peuvent souscrire pour recevoir les notifications.
Utilisation de l'observer pattern
Le modèle observateur peut nous aider dans les cas suivants :
- Quand une abstraction a deux aspects (représentations), l'un dépendant de l'autre. Encapsuler ces 2 représentations dans des objets distincts permet de les réutiliser et de les modifier indépendamment.
- Quand la modification d'un objet requiert la modification d'autres objets, et que nous ne savons combien d'objets doivent être modifiés.
- Quand un objet doit être capable de notifier d'autres objets sans faire d'hypothèses sur la nature de ces objets. En d'autres termes, nous ne désirons pas que ces objets soient trop fortement couplés.
Structure de l'observer pattern
Constituants de l'observer pattern
- Subject (sujet)
Connaît ses observateurs. N'importe quel nombre d'objets Observer peuvent observer un sujet.
Fournit une interface pour attacher et détacher des objets Observer. - Observer (observateur)
Définit une interface de mise à jour pour les objets qui peuvent être notifiés des changement dans un sujet. - ConcreteSubject (sujet concret)
Mémorise les états qui intéressent les objets ConcreteObserver.
Envoie une notification à ses observeurs lorsque son état est modifié. - ConcreteObserver (observateur concret)
Maintient une référence vers un objet ConcreteSubject.
Mémorise l'état qui doit rester consistent (pertinent, cohérent) pour le sujet.
Implémente l'interface de mise à jour de l'Observer pour maintenir son état consistent avec le sujet.
Collaborations de l'observer pattern
ConcreteSubject notifie ses observateurs quand un changement survient, qui pourrait rendre l'état des observateurs inconsistent avec le sien.
Après avoir été informé d'un changement dans le sujet concret, un objet ConcreteObserver peut interroger le sujet pour plus d'informations. ConcreteObserver utilise ces informations pour acorder son état avec celui du sujet.
Conséquences de l'observer pattern
L'Observer pattern permet de rendre indépendants les observateurs et la modification des objets. Nous pouvons réutiliser les objets Subject sans réutiliser leurs observateurs, et vice versa.
Avantages de l'observer pattern
Abstraction du couplage entre Subject et Observer. Tout ce que Subject sait, c'est qu'il gère un ensemble d'observateurs, cacun de ceux-ci se conformant à l'interface Observer. Le sujet ne doit pas avoir connaissance de la classe concrète de chaque observateur. Donc le couplace entre sujets et observateurs est abstrait et minimal.
Parce que Subject et Observer ne sont pas étroitement couplés, ils peuvent appartenir à différents niveaux d'abstraction dans un système. Un sujet bas niveau peut communiquer et informer un observateur haut niveau, en laissant intacte la structure en couches du système. Si Subject et Observer sont agrégés ensemble, alors l'objet résultant doit aussi fusionner deux couches (et violer la structure en couches), ou résider dans une couche ou dans l'autre (ce qui peut compromettre l'abstraction du système en couches).
Support de la diffusion. Contrairement à une requête ordinaire, la notification émise par un sujet n'a pas à spécifier ses destinataires. La notification est automatiquement diffusée à tous les objets intéressés qui ont souscrit chez lui. Le sujet ne se préoccupe pas du nombre d'objets intéressés qui existent; sa seule responsabilité est de notifier ses observateurs. Ceci nous donne la liberté d'ajouter ou de supprimer à tout instant des observateurs. C'est à l'observateur de donner suite à une notification ou de l'ignorer.
Contraintes de l'observer pattern
Mises à jour inopinées. Comme les observateurs n'ont pas connaissance de la présence des autres observateurs, ils peuvent être aveugles au coût final d'une modification du sujet. Une opération en apparence insignifiante peut engendrer une cascade de mises à jour des observateurs et des objets qui en dépendent. De plus, des critères d'interdépendances mal définis mènent généralement à des mises à jour erronées qui peuvent être difficiles à détecter.
Ce problème est agravé par le fait que le simple protocole de mise à jour (update) ne fournit aucun détail sur ce qui a été modifié dans le sujet. Sans protocole supplémentaire aidant les observateurs à découvrir ce qui a changé, ceux-ci peuvent être forcés à travailler énormément pour déterminer ce qui a changé.
Implémentation de l'observer pattern
Implémentation des mécanismes de dépendances dans le modèle observateur :
Etablir la correpondance (mapping) entre les sujets et leurs observateurs. Le moyen le plus simple pour un sujet de mémoriser les observateurs qu'il devra notifier, est de maintenir une référence explicite à chacun d'eux dans le sujet. Cependant, une telle mémorisation peut être trop coûteuse lorsqu'il y a beaucoup de sujets et peu d'observateurs. Une solution est de négocier le temps d'exécution en utilisant une recherche associative (associative look-up) - par exemple une hash table, pour maintenir une correspondance sujet - observateur. Ainsi, un sujet sans observateur ne risquera pas de surconsommer la mémoire. Par contre, ce type d'approche accroît le coût d'accès aux observateurs.
Observer plus d'un sujet. Il peut être intéressant pour un observateur de dépendre de plusieurs sujets. Par exemple, un tableur peut dépendre de plus d'une source de données. Il est nécessaire d'étendre l'interface de mise à jour pour permettre à l'utilisateur de déterminer quel sujet a envoyé la notification. Le sujet peut simplement se passer lui-même (passer sa référence en paramètre) dans l'opération de mise à jour (opération update chez l'Observer), indiquant ainsi le sujet à examiner.
Qui déclenche la mise à jour ? Le maintien de la cohérence entre sujets et observateurs repose sur le mécanisme de notification. Mais quel est l'objet qui appelle effectivement l'opération notify pour déclancher la mise à jour ? Il existe 2 options :
- Avoir une opération de paramètrage d'état du sujet, qui appelle l'opération notify après avoir changé l'état du sujet. L'avantage de cette approche est de soulager les clients d'avoir à se souvenir d'appeler notify sur le sujet. L'inconvénient est que plusieurs opérations consécutives vont entrainer des mises à jour consécutives, ce qui peut être inefficace.
- Confier aux clients la responsabilité d'invoquer notify au moment adéquoit. L'avantage est que le client peut attendre qu'une série de changements d'états aient eu lieu avant de déclancher l'opération de mise à jour, évitant ainsi des mises à jour intermédiaires inutiles. Le désavantage est que le clilent doit se voir ajouter la responsabilité de déclancher la mise à jour. Ceci augmente la probabilité d'erreurs, le client risquant d'oublier d'appeler notify.
Références périmées vers les sujets détruits. La suppression d'un objet ne doit pas laisser de références périmées dans ses observateurs. Une manière d'éviter les références périmées est de faire en sorte que le sujet, avant d'être détruit, en notifie ses observateurs, ainsi ils peuvent détruire la référence qu'ils maintenaient. Se contenter de détruire l'observateur n'est généralement pas la solution, car d'autres objets peuvent les référencer, ou ils peuvent encore observer d'autres objets.
S'assurer que l'état du sujet est cohérent avant la notification. Il est important de s'assurer que l'état du sujet soit cohérent avant d'appeler notify, car les observateurs interrogeront le sujet sur son état pendant leur phase de mise à jour (appel à l'opération update de l'observateur) de leur propre état.
Eviter les protocoles d'update spécifiques de l'observateur (push and pull model). Dans l'implémentation de l'Observer pattern, le sujet doit souvent diffuser des informations complémentaires sur les modifications. Le sujet transmet ces informations complémentaires sur les modifications.
Exemples de codes de l'observer pattern
Code c++ (observer Pattern) (70 lignes)
using System; using System.Collections; // Subject abstract class Subject{ // Fields private ArrayList observers = new ArrayList(); // Methods public void Attach(Observer observer){ observers.Add(observer); } public void Detach(Observer observer){ observers.Remove(observer); } public void Notify(){ foreach(Observer o in observers) o.Update(); } } // ConcreteSubject class ConcreteSubject : Subject{ // Fields private string subjectState; // Properties public string SubjectState{ get{ return subjectState; } set{ subjectState = value; } } } // Observer abstract class Observer{ // Methods abstract public void Update(); } // ConcreteObserver class ConcreteObserver : Observer{ // Fields private string name; private string observerState; private ConcreteSubject subject; // Constructors public ConcreteObserver(ConcreteSubject subject, string name){ this.subject = subject; this.name = name; } // Methods override public void Update(){ observerState = subject.SubjectState; Console.WriteLine( "Observer {0}'s new state is {1}",name,observerState ); } // Properties public ConcreteSubject Subject{ get { return subject; } set { subject = value; } } } /// Client test public class Client{ public static void Main(string[] args){ // Configure Observer structure ConcreteSubject s = new ConcreteSubject(); s.Attach( new ConcreteObserver(s,"X") ); s.Attach( new ConcreteObserver(s,"Y") ); s.Attach( new ConcreteObserver(s,"Z") ); // Change subject and notify observers s.SubjectState = "ABC"; s.Notify(); } }
Ce qui produit en sortie :
Observer X's new state is ABC
Observer Y's new state is ABC
Observer Z's new state is ABC
Version en cache
21/11/2024 09:56:05 Cette version de la page est en cache (à la date du 21/11/2024 09:56:05) afin d'accélérer le traitement. Vous pouvez activer le mode utilisateur dans le menu en haut pour afficher la dernère version de la page.Document créé le 15/10/2005, dernière modification le 26/10/2018
Source du document imprimé : https://www.gaudry.be/pattern-observer.html
L'infobrol est un site personnel dont le contenu n'engage que moi. Le texte est mis à disposition sous licence CreativeCommons(BY-NC-SA). Plus d'info sur les conditions d'utilisation et sur l'auteur.