Observateur (patron de conception)

Page d’aide sur l’homonymie

Pour les articles homonymes, voir Observateur.

Le patron observateur est un patron de conception de la famille des patrons comportementaux. Il s'agit de l'un des vingt-trois patrons de l'ouvrage du « Gang of Four » Design Patterns – Elements of Reusable Object-Oriented Software[1].

Il est utilisé pour envoyer un signal à des modules qui jouent le rôle d'observateurs. En cas de notification, les observateurs effectuent alors l'action adéquate en fonction des informations qui parviennent depuis les modules qu'ils observent (les observables).

Utilité

Les notions d'observateur et d'observable permettent de limiter le couplage entre les modules aux seuls phénomènes à observer. Le patron permet aussi une gestion simplifiée d'observateurs multiples sur un même objet observable.

Il est recommandé dès qu'il est nécessaire de gérer des évènements, quand une classe déclenche l'exécution d'une ou plusieurs autres.

Structure

Dans ce patron, le sujet observable se voit attribuer une collection d'observateurs qu'il notifie lors de changements d'états. Chaque observateur concret est chargé de faire les mises à jour adéquates en fonction des changements notifiés.

Ainsi, l'observé n'est pas responsable des changements qu'il entraîne sur les observateurs.

Diagramme UML du patron de conception Observateur.

Illustration

Par exemple, une classe produit des signaux (données observables), visualisée à travers des panneaux (observateurs) d'une interface graphique. La mise à jour d'un signal modifie le panneau qui l'affiche. Afin d'éviter l'utilisation de thread ou encore d'inclure la notion de panneau dans les signaux, il suffit d'utiliser le patron de conception observateur.

Le principe est que chaque classe observable contient une liste d'observateurs ; ainsi, à l'aide d'une méthode de notification, l'ensemble des observateurs est prévenu. La classe observée hérite de Observable qui gère la liste des observateurs. La classe Observateur est quant à elle purement abstraite, la fonction de mise à jour ne pouvant être définie que par une classe spécialisée.

Exemple en langage Java

L'exemple montre comment utiliser l'API du langage Java qui propose des interfaces et des objets abstraits liés à ce patron de conception.

  • Créer une classe qui étend java.util.Observable[2] et dont la méthode de mise à jour des données setData lance une notification des observateurs (1) :

Néanmoins, il faut noter que la classe java.util.Observable est obsolète depuis la sortie de Java 9.

class Signal extends Observable {

  void setData(byte[] lbData){
    setChanged(); // Positionne son indicateur de changement
    notifyObservers(); // (1) notification
  }
}
  • Le panneau d'affichage qui implémente l'interface java.util.Observer est créé. Avec une méthode d'initialisation (2), il lui est transmis le signal à observer (2). Lorsque le signal notifie une mise à jour, le panneau est redessiné (3).
class JPanelSignal extends JPanel implements Observer {

  void init(Signal lSigAObserver) {
    lSigAObserver.addObserver(this); // (2) ajout d'observateur
  }

  void update(Observable observable, Object objectConcerne) {
    repaint();  // (3) traitement de l'observation
  }
}

Exemple en langage C++

Dans cet exemple en C++, les événements qui se produisent dans une classe Exemple sont affichés.

Exemple en C++
#include <string>
#include <set>
#include <iostream>

class IObserver
{
public:
	virtual void update(string data) = 0;
};

class Observable
{
private:
	std::set<IObserver*> list_observers;

public:
	void notify(string data) const
	{
		// Notifier tous les observers
      for (auto & observer : list_observers)
			observer->update(data);
	}

	void addObserver(IObserver* observer)
	{
		// Ajouter un observer a la liste
		list_observers.insert(observer);
	}

	void removeObserver(IObserver* observer)
	{
		// Enlever un observer a la liste
		list_observers.erase(observer);
	}
};

class Display : public IObserver
{
	void update(string data)
	{
		std::cout << "Evenement : " << data << std::endl;
	}
};

class Exemple : public Observable
{
public:
	void message(string message)
	{
		// Lancer un evenement lors de la reception d'un message
		notify(message);
	}
};

int main()
{
	Display display;
	Exemple exemple;

	// On veut que "Display" soit prévenu à chaque réception d'un message dans "Exemple"
	exemple.addObserver(&display);

	// On envoie un message a Exemple
	exemple.message("réception d'un message"); // Sera affiché par Display

	return 0;
}
 

Exemple en langage C#

Tout comme Iterateur, Observateur est implémenté en C# par l'intermédiaire du mot clé event. La syntaxe a été simplifiée pour l'abonnement ou appel d'une méthode sur levée d'un événement. Un événement possède une signature : le type de la méthode que doit lever l'évènement. Dans cet exemple c'est EventHandler.

event EventHandler observable;

La signature du type délégué EventHandler est « void (object emetteur, EventArgs argument) ».

Exemple en C#
using System;
///<summary> un observateur </summary>

class Kevin
{
    public void Reception(object sender, EventArgs e)
    {
        Console.WriteLine("Kevin a reçu: {1} de: {0}", sender.ToString(), e.ToString());
        Console.ReadKey();
    }
}
class Program
{
    ///<summary> la liste d'abonnés </summary>
    static event EventHandler observable;

    static void Main()
    {
        var kevin = new Kevin();

        // enregistrement de Kevin dans la liste d'abonnés
        observable += new EventHandler(kevin.Reception);

        // si la liste n'est pas vide, prévenir les abonnés
        if (observable != null)
            observable(AppDomain.CurrentDomain, new BiereEventArgs() { Bouteilles = 2 });
    }
}
/// <summary> que du fonctionnel </summary>
class BiereEventArgs : EventArgs
{
    public uint Bouteilles;
    public override string ToString()
    {
        return string.Format("{0} bouteille{1}",
            (Bouteilles > 0) ? Bouteilles.ToString() : "Plus de",
            (Bouteilles > 1) ? "s" : string.Empty);
    }
}
 

Exemple en langage Ruby

La classe observable implémente le patron de conception observateur.

Exemple avec le framework Ruby On Rails

Rails fournit un générateur pour ce patron de conception. Il s'utilise comme ceci:

/usr/local/projetsawd# script/generate observer essai
 exists app/models/
 exists test/unit/
 create app/models/essai_observer.rb
 create test/unit/essai_observer_test.rb
Exemple avec Ruby On Rails
 class AuditeurObserver < ActiveRecord::Observer
observe Lignepaatec

def after_create(ligne)
 @projet= Projet.find(ligne.projet_id)
 @projet.paa+=1
 @projet.save
end
def after_destroy(ligne)
 @projet= Projet.find(ligne.projet_id)
 @projet.paa-=1
 @projet.save
end

end
 

Exemple en langage Delphi

source : Delphi GOF DesignPatterns (CodePlex)

Exemple en Delphi
unit observer;

interface

uses Classes, SysUtils;

type

 IObserver = interface
 ['{A3208B98-3F48-40C6-9986-43B6CB8F4A7E}']
   procedure Update(Subject: TObject);
 end;

 ISubject = interface
 ['{CA063853-73A8-4AFE-8CAA-50600996ADEB}']
   procedure Attach(Observer: IObserver);
   procedure Detach(Observer: IObserver);
   procedure Notify;
 end;

 TStock = class(TInterfacedObject, ISubject)
 private
   FSymbol: string;
   FPrice: Double;
   FInvestors: TInterfaceList;
   function ReadSymbol: string;
   function ReadPrice: Double;
   procedure SetPrice(value: Double);
 public
   constructor Create(symbol: string; price: Double);
   destructor Destroy; override;
   procedure Attach(Observer: IObserver);
   procedure Detach(Observer: IObserver);
   procedure Notify;
   property Symbol: string read ReadSymbol;
   property Price: Double read ReadPrice write SetPrice;
 end;

 TInvestor = class(TINterfacedObject, IObserver)
 private
   FName: string;
 public
   constructor Create(name: string);
   procedure Update(Subject: TObject);
 end;

implementation

{ TStock }

procedure TStock.Attach(Observer: IObserver);
begin
 if FInvestors = nil then
   FInvestors := TInterfaceList.Create;
 if FInvestors.IndexOf(Observer) < 0 then
   FInvestors.Add(Observer);
end;

constructor TStock.Create(symbol: string; price: Double);
begin
 FSymbol := symbol;
 FPrice := price;
end;

destructor TStock.Destroy;
begin
 FInvestors.Free;
 inherited;
end;

procedure TStock.Detach(Observer: IObserver);
begin
 if FInvestors <> nil then
 begin
   FInvestors.Remove(Observer);
   if FInvestors.Count = 0 then
   begin
     FInvestors.Free;
     FInvestors := nil;
   end;
 end;
end;

procedure TStock.Notify;
var
 i: Integer;
begin
 if FInvestors <> nil then
   for i := 0 to Pred(FInvestors.Count) do
     IObserver(FInvestors[i]).Update(Self);
end;

function TStock.ReadPrice: Double;
begin
 Result := FPrice;
end;

function TStock.ReadSymbol: string;
begin
 Result := FSymbol
end;

procedure TStock.SetPrice(value: Double);
begin
 if value <> FPrice then begin
   FPrice := value;
   Notify;
 end;
end;

{ TInvestor }

constructor TInvestor.Create(name: string);
begin
 FName := name;
end;

procedure TInvestor.Update(Subject: TObject);
begin
 WriteLn(Format('Notified %s of %s change to %g', [FName, TStock(Subject).Symbol, TStock(Subject).Price]));
end;

end.

{ projet }

program Behavioral.Observer.Pattern;

{$APPTYPE CONSOLE}

uses
 SysUtils,
 Pattern in 'Pattern.pas';

var
 stock: TStock;
 investor1, investor2: TInvestor;

begin
 try
   stock := TStock.Create('IBM', 120.0);
   investor1 := TInvestor.Create('Sorros');
   investor2 := TInvestor.Create('Berkshire');
   try
     stock.Attach(investor1);
     stock.Attach(investor2);

     stock.Price := 120.10;
     stock.Price := 121.0;
     stock.Price := 120.50;
     stock.Price := 120.750;

     WriteLn(#10 + 'Remove investor');
     stock.Detach(investor1);

     stock.Price := 120.10;
     stock.Price := 121.0;
     stock.Price := 120.50;
     stock.Price := 120.750;

     stock.Detach(investor2);

     ReadLn;
   finally
     stock.Free;
   end;
 except
   on E:Exception do
     Writeln(E.Classname, ': ', E.Message);
 end;
end.
 

Notes et références

  1. Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides (trad. Jean-Marie Lasvergères), Design Patterns - Catalogue de modèles de conceptions réutilisables, France, Vuibert, , 490 p. [détail des éditions] (ISBN 2-71178-644-7)
  2. (en) « Observable (Java Platform SE 7 ) », sur oracle.com (consulté le ).
v · m
Patrons de conception
Création
  • Fabrique abstraite
  • Monteur
  • Fabrique
  • Prototype
  • Singleton
Structure
  • Adaptateur
  • Pont
  • Composite
  • Décorateur
  • Façade
  • Poids-mouche
  • Proxy
Comportement
Fonctionnel
Patron d'architecture
Autres patrons
  • icône décorative Portail de l’informatique