Design Pattern Observer

Design Pattern Observer

Description

Design Pattern Observer C#
Présentation du pattern Observer très connu avec un petit exemple simple à l'appui pour illustrer le concept.

Préambule

Autres design pattern:

Strategy
Command

Introduction

Un design pattern est un concept destiné à résoudre des problèmes récurrents suivant un certain paradigme (dans notre cas, celui de la programmation orienté objet). Il existe des dizaines de patterns différents qui simplifient grandement la vie du développeur.

Pour ce premier tutorial sur les designs patterns, j'ai choisi d'expliquer et de proposer une implémentation via un exemple du design pattern Observer. Ce pattern est utilisé pour observer l'état d'un objet dans un programme.

Voici à quoi ressemble ce pattern d'un point de vue UML

Implémentation

Rien ne vaut un petit exemple simple et pratique pour mettre en oeuvre ce pattern. Imaginons que nous avons des articles en vente et que nous avons également des clients qui vont être intéressés par ces objets.
Si le prix d'un article mis en vente varie, il faudrait que les clients puissent être informés de ce changement.

Voici l'implémentation que je propose pour ce problème.
Pour commencer, l'observer, c'est-à-dire l'interface qui va nous mettre à disposition une méthode update.

using System;

namespace DPObserver
{
    /// ------------------------------------------------------------------
    /// <summary>
    /// Represents the observer object.
    /// </summary>
    /// ------------------------------------------------------------------
    public interface IClient
    {

    /// ---------------------------------------------------------------
    /// <summary>
    /// The article has been updated.
    /// </summary>
    /// ---------------------------------------------------------------
    void Update(DefaultArticle defaultArticle);

    }
}

Ensuite, notre classe Client qui va implémenter cette interface

namespace DPObserver
{
    /// ------------------------------------------------------------------
    /// <summary>
    /// Represents the concrete observer.
    /// </summary>
    /// ------------------------------------------------------------------
    public class Client : IClient
    {
        private string _name = null;

        /// ---------------------------------------------------------------
        /// <summary>
        /// Create a new client.
        /// </summary>
        /// ---------------------------------------------------------------
        public Client(string name)
        {
            this._name = name;
        }

        /// ---------------------------------------------------------------
        /// <summary>
        /// Get the client's name.
        /// </summary>
        /// ---------------------------------------------------------------
        public string Name
        {
            get { return this._name; }
        }

        /// ---------------------------------------------------------------
        /// <summary>
        /// 
        /// </summary>
        /// ---------------------------------------------------------------
        public void Update(DefaultArticle defaultArticle)
        {
            Console.WriteLine("Client {0} notified! Article {1}: new price = {2}", this._name, defaultArticle.Name, defaultArticle.Price);
        }
    }
}

La partie 'Observer' est donc très simple. Passons maintenant à l'implémentation du sujet. On commence par la classe abstraite qui nous définies les méthodes 'd'abonnements'.

namespace DPObserver
{
    /// ------------------------------------------------------------------
    /// <summary>
    /// Represents the subject object.
    /// </summary>
    /// ------------------------------------------------------------------
    public abstract class DefaultArticle
    {
        private List<IClient> _observers = new List<IClient>();
        protected float _price = 0f;
        protected string _name = String.Empty;

        /// ---------------------------------------------------------------
        /// <summary>
        /// Get or set the price.
        /// </summary>
        /// ---------------------------------------------------------------
        public float Price
        {
        get { return this._price; }
        set
        {
            this._price = value;
            this.NotifyObservers();
        }
        }

        /// ---------------------------------------------------------------
        /// <summary>
        /// Get the name.
        /// </summary>
        /// ---------------------------------------------------------------
        public string Name
        {
            get { return this._name; }
        }

        /// ---------------------------------------------------------------
        /// <summary>
        /// Attach an observer.
        /// </summary>
        /// <param name="client">The observer to attach.</param>
        /// ---------------------------------------------------------------
        public void Attach(IClient client)
        {
            if (!this._observers.Contains(client)) this._observers.Add(client);
        }


        /// ---------------------------------------------------------------
        /// <summary>
        /// Detach an observer.
        /// </summary>
        /// <param name="client">The observer to dettach.</param>
        /// ---------------------------------------------------------------
        public void Detach(IClient client)
        {
            if (this._observers.Contains(client)) this._observers.Remove(client);
        }

        /// ---------------------------------------------------------------
        /// <summary>
        /// Notify all observers.
        /// </summary>
        /// ---------------------------------------------------------------
        protected void NotifyObservers()
        {
            foreach (IClient obs in this._observers) obs.Update(this);
        }
    }
}

Puis, pour terminé, notre Article qui est l'implémentation concrète

namespace DPObserver
{
    /// ------------------------------------------------------------------
    /// <summary>
    /// Represents the concrete subject.
    /// </summary>
    /// ------------------------------------------------------------------
    public class Article : DefaultArticle
    {
        /// ---------------------------------------------------------------
        /// <summary>
        /// Create a new article.
        /// </summary>
        /// <param name="name"></param>
        /// <param name="price"></param>
        /// ---------------------------------------------------------------
        public Article(string name)
        {
            this._name = name;
        }
    }
}

Et voilà, on a déjà mis en place le mécanisme pour que notre client soit notifié des changements. Ecrivons quelques lignes des tests :

namespace DPObserver
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var article1 = new Article("CodeS-SourceS") { Price = 1234.50f };
            var article2 = new Article("Livre C#") { Price = 432.10f };
            var article3 = new Article("Abonnement MSDN") { Price = 12345.60f };
            var article4 = new Article("Camion poubelle") { Price = 123.4f };
            var article5 = new Article("Sifflet") { Price = 12f };

            var client1 = new Client("Bidou");
            var client2 = new Client("Nix");

            article1.Attach(client1);
            article1.Attach(client2);
            article1.Price = 88.5f;
            article1.Detach(client1);
            article1.Price = 73.3f;

            article2.Attach(client1);
            article2.Attach(client2);
            article2.Price = 400f;

            article3.Attach(client1);
            article3.Price = 14000f;

            article5.Attach(client2);
            article5.Price = 12f;
        }
    }
}

Les résultats sont bien ceux attendus :

Conclusion

Ce pattern est assez utilisé et pas très compliqué à mettre en place. Dans la réalité, le cas sera certainement plus complexe. On peut par exemple tout à fait imaginer que l'objet qui joue le rôle d'observateur soit en même temps le sujet d'un autre observateur, et qu'on ait ainsi une chaîne entre les différents objets.

A voir également
Ce document intitulé « Design Pattern Observer » issu de CodeS SourceS (codes-sources.commentcamarche.net) est mis à disposition sous les termes de la licence Creative Commons. Vous pouvez copier, modifier des copies de cette page, dans les conditions fixées par la licence, tant que cette note apparaît clairement.
Rejoignez-nous