Le modèle observateur(Observer Pattern)

Le pattern observer selon l'ouvrage "Design Patterns: Elements of Reusable Object-Oriented Software" du GOF.

Alias

  • « Observateur » (en anglais, “Observer”)
  • « Dépendants » (en anglais, “Dependents”)
  • « Diffusion » (en anglais, “Publish”)
  • « Souscription » (en anglais, “Subscribe”)

Table des matières Haut

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.

Table des matières Haut

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.

Table des matières Haut

Structure de l'observer pattern

GOF exemple d'observer pattern

Table des matières Haut

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.

Table des matières Haut

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.

Table des matières Haut

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é.

Table des matières Haut

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.

Table des matières Haut

Exemples de codes de l'observer pattern

  1. using System;
  2. using System.Collections;
  3. // Subject
  4. abstract class Subject{
  5. // Fields
  6. private ArrayList observers = new ArrayList();
  7. // Methods
  8. public void Attach(Observer observer){
  9. observers.Add(observer);
  10. }
  11. public void Detach(Observer observer){
  12. observers.Remove(observer);
  13. }
  14. public void Notify(){
  15. foreach(Observer o in observers)
  16. o.Update();
  17. }
  18. }
  19. // ConcreteSubject
  20. class ConcreteSubject : Subject{
  21. // Fields
  22. private string subjectState;
  23. // Properties
  24. public string SubjectState{
  25. get{ return subjectState; }
  26. set{ subjectState = value; }
  27. }
  28. }
  29. // Observer
  30. abstract class Observer{
  31. // Methods
  32. abstract public void Update();
  33. }
  34. // ConcreteObserver
  35. class ConcreteObserver : Observer{
  36. // Fields
  37. private string name;
  38. private string observerState;
  39. private ConcreteSubject subject;
  40. // Constructors
  41. public ConcreteObserver(ConcreteSubject subject, string name){
  42. this.subject = subject;
  43. this.name = name;
  44. }
  45. // Methods
  46. override public void Update(){
  47. observerState = subject.SubjectState;
  48. Console.WriteLine(
  49. "Observer {0}'s new state is {1}",name,observerState
  50. );
  51. }
  52. // Properties
  53. public ConcreteSubject Subject{
  54. get { return subject; }
  55. set { subject = value; }
  56. }
  57. }
  58. /// Client test
  59. public class Client{
  60. public static void Main(string[] args){
  61. // Configure Observer structure
  62. ConcreteSubject s = new ConcreteSubject();
  63. s.Attach( new ConcreteObserver(s,"X") );
  64. s.Attach( new ConcreteObserver(s,"Y") );
  65. s.Attach( new ConcreteObserver(s,"Z") );
  66. // Change subject and notify observers
  67. s.SubjectState = "ABC";
  68. s.Notify();
  69. }
  70. }

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/12/2024 00:12:32 Cette version de la page est en cache (à la date du 21/12/2024 00:12:32) 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.

Notes
  1.  Observateur : correspond à “Observer” en anglais

  2.  Dépendants : correspond à “Dependents” en anglais

  3.  Diffusion : correspond à “Publish” en anglais

  4.  Souscription : correspond à “Subscribe” en anglais

Table des matières Haut