Bibliobrol : Observer pattern en C#
Nous avons souvent besoin de contrôler le travail effectué par les méthodes des différentes classes de notre application. Nous allons donc créer une classe Notification, qui nous permettra de placer différentes informations qui nous renseigneront sur les différentes opérations effectuées dans l'application :
- sender : un objet qui nous permet de déterminer quelle est la classe qui lance une notification.
- title : une courte chaîne de caractères.
- msg : le message qui est envoyé
- e : une exception qui nous permet de déterminer à quelle ligne de quelle classe se situe l'erreur, le message d'erreur, la trace pour remonter le cours des évènements qui ont conduit à l'erreur, etc.
- verbose : le niveau de notification
Code c# (Notification.cs) (161 lignes)
using System; namespace be.gaudry.observer { public class Notification { #region declarations /// <summary> /// Level of notification. May be used to deal only with some notifications. /// </summary> public enum VERBOSE { /// <summary> /// sql query or another persistence operation /// </summary> persistentOperation, /// <summary> /// any operation /// </summary> basicOperation, /// <summary> /// feedback of an operation /// </summary> opsResult, /// <summary> /// requests syntax to debug /// </summary> advancedOperation, /// <summary> /// info to debug /// </summary> debug, /// <summary> /// any general error /// </summary> error, /// <summary> /// may not interrupt application /// </summary> lowError, /// <summary> /// application must be interrupt /// </summary> criticalError, /// <summary> /// used to notify without display info /// </summary> internalNotification, /// <summary> /// used to notify a modification without display info /// </summary> modified, /// <summary> /// used to store errors and not display it /// </summary> hideErrors, /// <summary> /// used to clean stored errors and adopt normal behaviour /// </summary> showNewErrors, /// <summary> /// used to display stored errors /// </summary> showErrors } private String title, msg; private Exception e; private VERBOSE verbose; private Object sender; #endregion #region constructors public Notification(String msg) : this(VERBOSE.basicOperation, "Information", msg) { } public Notification(String msg, Object sender) : this(VERBOSE.basicOperation, "Information", msg, sender) { } public Notification(String title, String msg) : this(VERBOSE.basicOperation, title, msg) { } public Notification(String title, String msg, Object sender) : this(VERBOSE.basicOperation, title, msg, sender) { } public Notification(VERBOSE level, String title, String msg, Object sender) : this(level, title, msg) { this.sender = sender; } public Notification(VERBOSE level, String msg, Object sender) : this(level, "", msg, sender) { } public Notification(VERBOSE level, String title, String msg) { this.verbose = level; this.title = title; this.msg = msg; } public Notification(VERBOSE level, String title, String msg, Exception e) : this(level, title, msg) { this.e = e; } public Notification(VERBOSE level, String title, String msg, Exception e, Object sender) : this(level, title, msg, e) { this.sender = sender; } public Notification(VERBOSE level, Exception e) : this(level, "Erreur", "", e) { } public Notification(VERBOSE level, Exception e, Object sender) : this(level, "Erreur", "", e, sender) { } public Notification(VERBOSE level, String title, Exception e) : this(level, title, "", e) { } public Notification(VERBOSE level, String title, Exception e, Object sender) : this(level, title, "", e, sender) { } public Notification(Exception e) : this(VERBOSE.error, "Erreur", "", e) { } public Notification(Exception e, Object sender) : this(VERBOSE.error, "Erreur", "", e, sender) { } #endregion #region getters and setters public String Title { set { this.title = value; } get { return this.title; } } public String Msg { set { this.msg = value; } get { return this.msg; } } public Exception NotificationException { set { this.e = value; } get { return this.e; } } public VERBOSE Level { set { this.verbose = value; } get { return this.verbose; } } public Object Sender { set { this.sender = value; } get { return this.sender; } } #endregion } }
Nous allons récupérer ces notifications, et les afficher dans notre console.
Non. Si nous décidons par la suite d'afficher les notifications dans un autre composant, ou de ne pas utiliser notre console, nous devrions modifier le code dans toutes les classes, à chaque fois que nous lançons une notification.
Le pattern observer vient à notre aide dans ce cas. Nous pouvons utiliser les délégués (une très bonne pratique du C#), mais nous allons ici calquer au plus près le pattern et réimplémenter nos classes.
C'est à la classe qui contient les méthodes à surveiller que revient la responsabilité de signaler les modifications. Comme il est impensable que le modèle connaisse la vue, les classes de la vue qui souhaitent être averties des modifications (Observer, ou observateur) doivent s'enregistrer auprès de la classe qui signale (Observable).
Code c# (Observable.cs) (56 lignes)
using System; using System.Collections.Generic; namespace be.gaudry.observer { /// <summary> /// Provides methods to add or remove observers and notify them /// </summary> [Serializable] public class Observable : IObservable { #region IObservable Members /// <summary> /// Send a notification to all registerd obervers /// </summary> /// <param name="notification">notification to send</param> public void notify(Notification notification) { try { foreach (IObserver observer in observers) { try { observer.update(notification); } catch (Exception) { } } } catch (InvalidOperationException) { } } /// <summary> /// Add (register) an observer /// </summary> /// <param name="o">observer to add</param> public void addObserver(IObserver o) { if(!observers.Contains(o)) observers.Add(o); } /// <summary> /// Remove an observer /// </summary> /// <param name="o">observer to remove</param> public void removeObserver(IObserver o) { observers.Remove(o); } #endregion } }
Afin de regrouper toutes les classes qui s'enregistrent, chaque observateur devra implémenter l'interface IObserver, qui l'oblige à implémenter une méthode update qui effectue un traitement ou non en fonction de l'évènement signalé. Nous allons donc forcer la méthode à prendre un argument qui sera une instance de la classe Notification.
Code c# (IObserver.cs) (7 lignes)
namespace be.gaudry.observer { public interface IObserver { void update(Notification notification); } }
Notre observable devra parcourir la collection d'observateur, et signaler l'évènement à chacun en appelant leur méthode update. Nous nommerons cette méthode notify.
De plus, l'observable doit fournir 2 méthodes pour ajouter et enlever un observateur.
Code c# (IObservable.cs) (26 lignes)
namespace be.gaudry.observer { /// <summary> /// Defines methods witch must be implemented for an observable object /// </summary> public interface IObservable { /// <summary> /// Sends a notification to all registerd obervers /// </summary> /// <param name="notification">notification to send</param> void notify(Notification notification); /// <summary> /// Adds (register) an observer /// </summary> /// <param name="o">observer to add</param> void addObserver(IObserver o); /// <summary> /// Removes an observer /// </summary> /// <param name="o">observer to remove</param> void removeObserver(IObserver o); } }
Nous allons donc créer une interface IObservable, mais aussi une classe Observable. Il suffit qu'une classe hérite d'Observable pour pouvoir diffuser les informations nécessaires. Si la classe hérite déjà d'une autre classe, elle devra alors implémenter l'interface IObservable, qui nous oblige alors à écrire les méthodes nécessaires.
Dans ce cas, l'observateur peut observer la classe singleton StaticObservable, et les classes qui doivent effectuer les notifications peuvent simplement appeler une méthode statique de cette classe.
Cette page contiendra des explications plus détaillées par la suite (quand j'aurais un moment de libre).
Code c# (StaticObservable.cs) (90 lignes)
namespace be.gaudry.observer { /// <summary> /// Singleton used to add observable features into a class (the observer may not know the class, it must know only this). /// </summary> public class StaticObservable { #region singleton private static Observable instance = null; private StaticObservable() { } /// <summary> /// /// </summary> private static Observable Instance { get { lock (padlock) { if (instance == null) { } return instance; } } } #endregion #region observable methods /// <summary> /// Send a notification to all registerd obervers /// </summary> /// <param name="notification">notification to send</param> public static void notify(Notification notification) { StaticObservable.Instance.notify(notification); } /// <summary> /// Add (register) an observer /// </summary> /// <param name="o">observer to add</param> public static void addObserver(IObserver o) { StaticObservable.Instance.addObserver(o); } /// <summary> /// Remove an observer /// </summary> /// <param name="o">observer to remove</param> public static void removeObserver(IObserver o) { StaticObservable.Instance.removeObserver(o); } #endregion #region static helper methods /// <summary> /// store errors and not display it /// </summary> /// <param name="notification">notification to send</param> public static void hideErrors() { } /// <summary> /// display stored errors /// </summary> /// <param name="notification">notification to send</param> public static void showErrors() { } /// <summary> /// lean stored errors and adopt normal behaviour /// </summary> /// <param name="notification">notification to send</param> public static void showNewErrors() { } #endregion } }
Version en cache
21/11/2024 09:30:59 Cette version de la page est en cache (à la date du 21/11/2024 09:30:59) 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 16/12/2006, dernière modification le 07/04/2023
Source du document imprimé : https://www.gaudry.be/bibliobrol-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.