Comment : implémenter un fournisseur
Le modèle de design observateur requiert une division entre un fournisseur, qui surveille les données et envoie des notifications, et un ou plusieurs observateurs, qui reçoivent des notifications (rappels) du fournisseur. Cette rubrique décrit comment créer un fournisseur. Une rubrique connexe, Guide pratique pour implémenter une méthode Observer, explique comment créer un observateur.
Pour créer un fournisseur
Définissez les données que le fournisseur est chargé d’envoyer aux observateurs. Bien que le fournisseur et les données qu’il envoie aux observateurs puissent être de type unique, ils sont généralement représentés par différents types. Par exemple, dans une application de surveillance de la température, la structure
Temperature
définit les données que le fournisseur (qui est représenté par la classeTemperatureMonitor
définie à l’étape suivante) surveille et auxquelles les observateurs s’abonnent.using System; public struct Temperature { private decimal temp; private DateTime tempDate; public Temperature(decimal temperature, DateTime dateAndTime) { this.temp = temperature; this.tempDate = dateAndTime; } public decimal Degrees { get { return this.temp; } } public DateTime Date { get { return this.tempDate; } } }
Public Structure Temperature Private temp As Decimal Private tempDate As DateTime Public Sub New(ByVal temperature As Decimal, ByVal dateAndTime As DateTime) Me.temp = temperature Me.tempDate = dateAndTime End Sub Public ReadOnly Property Degrees As Decimal Get Return Me.temp End Get End Property Public ReadOnly Property [Date] As DateTime Get Return tempDate End Get End Property End Structure
Définissez le fournisseur de données, qui est un type qui implémente l’interface System.IObservable<T>. L’argument de type générique du fournisseur est le type que le fournisseur envoie aux observateurs. L’exemple suivant définit une classe
TemperatureMonitor
, qui est une implémentation System.IObservable<T> construite avec un argument de type génériqueTemperature
.using System; using System.Collections.Generic; public class TemperatureMonitor : IObservable<Temperature> {
Imports System.Collections.Generic Public Class TemperatureMonitor : Implements IObservable(Of Temperature)
Déterminez comment le fournisseur stockera les références aux observateurs afin que chaque observateur puisse être averti lorsque nécessaire. Plus couramment, un objet de collection comme un objet List<T> générique est utilisé à cet effet. L’exemple suivant définit un objet List<T> privé instancié dans le constructeur de classe
TemperatureMonitor
.using System; using System.Collections.Generic; public class TemperatureMonitor : IObservable<Temperature> { List<IObserver<Temperature>> observers; public TemperatureMonitor() { observers = new List<IObserver<Temperature>>(); }
Imports System.Collections.Generic Public Class TemperatureMonitor : Implements IObservable(Of Temperature) Dim observers As List(Of IObserver(Of Temperature)) Public Sub New() observers = New List(Of IObserver(Of Temperature)) End Sub
Définissez une implémentation IDisposable que le fournisseur peut retourner aux abonnés afin qu’ils cessent de recevoir des notifications à tout moment. L’exemple suivant définit une classe
Unsubscriber
imbriquée à laquelle une référence à la collection d’abonnés et à l’abonné est passée lorsque la classe est instanciée. Ce code permet à l’abonné d’appeler l’implémentation IDisposable.Dispose de l’objet pour se supprimer lui-même de la collection d’abonnés.private class Unsubscriber : IDisposable { private List<IObserver<Temperature>> _observers; private IObserver<Temperature> _observer; public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer) { this._observers = observers; this._observer = observer; } public void Dispose() { if (! (_observer == null)) _observers.Remove(_observer); } }
Private Class Unsubscriber : Implements IDisposable Private _observers As List(Of IObserver(Of Temperature)) Private _observer As IObserver(Of Temperature) Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As IObserver(Of Temperature)) Me._observers = observers Me._observer = observer End Sub Public Sub Dispose() Implements IDisposable.Dispose If _observer IsNot Nothing Then _observers.Remove(_observer) End Sub End Class
Implémentez la méthode IObservable<T>.Subscribe. Une référence à l’interface System.IObserver<T> est passée à la méthode et doit être stockée dans l’objet conçu à cet effet à l’étape 3. La méthode doit ensuite retourner l’implémentation IDisposable développée à l’étape 4. L’exemple suivant illustre l’implémentation de la méthode Subscribe dans la classe
TemperatureMonitor
.public IDisposable Subscribe(IObserver<Temperature> observer) { if (! observers.Contains(observer)) observers.Add(observer); return new Unsubscriber(observers, observer); }
Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable Implements System.IObservable(Of Temperature).Subscribe If Not observers.Contains(observer) Then observers.Add(observer) End If Return New Unsubscriber(observers, observer) End Function
Informez les observateurs si nécessaire en appelant leurs implémentations IObserver<T>.OnNext, IObserver<T>.OnError et IObserver<T>.OnCompleted. Dans certains cas, un fournisseur peut ne pas appeler la méthode OnError lorsqu’une erreur se produit. Par exemple, la méthode
GetTemperature
suivante simule un moniteur qui lit les données de température toutes les cinq secondes et avertit les observateurs si la température a changé d’au moins 1 degré depuis la lecture précédente. Si l’appareil ne signale pas de température (autrement dit, si sa valeur est Null), le fournisseur avertit les observateurs que la transmission est terminée. Notez que, en plus d’appeler la méthode OnCompleted de chaque observateur, la méthodeGetTemperature
efface la collection List<T>. Dans ce cas, le fournisseur ne fait aucun appel à la méthode OnError de ses observateurs.public void GetTemperature() { // Create an array of sample data to mimic a temperature device. Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m, 15.4m, 15.45m, null }; // Store the previous temperature, so notification is only sent after at least .1 change. Nullable<Decimal> previous = null; bool start = true; foreach (var temp in temps) { System.Threading.Thread.Sleep(2500); if (temp.HasValue) { if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) { Temperature tempData = new Temperature(temp.Value, DateTime.Now); foreach (var observer in observers) observer.OnNext(tempData); previous = temp; if (start) start = false; } } else { foreach (var observer in observers.ToArray()) if (observer != null) observer.OnCompleted(); observers.Clear(); break; } } }
Public Sub GetTemperature() ' Create an array of sample data to mimic a temperature device. Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D, 15.4D, 15.45D, Nothing} ' Store the previous temperature, so notification is only sent after at least .1 change. Dim previous As Nullable(Of Decimal) Dim start As Boolean = True For Each temp In temps System.Threading.Thread.Sleep(2500) If temp.HasValue Then If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then Dim tempData As New Temperature(temp.Value, Date.Now) For Each observer In observers observer.OnNext(tempData) Next previous = temp If start Then start = False End If Else For Each observer In observers.ToArray() If observer IsNot Nothing Then observer.OnCompleted() Next observers.Clear() Exit For End If Next End Sub
Exemple
L’exemple suivant contient le code source complet permettant de définir une implémentation IObservable<T> pour une application de surveillance de la température. Il inclut la structure Temperature
, qui correspond aux données envoyées aux observateurs, et la classe TemperatureMonitor
, qui est l’implémentation IObservable<T>.
using System.Threading;
using System;
using System.Collections.Generic;
public class TemperatureMonitor : IObservable<Temperature>
{
List<IObserver<Temperature>> observers;
public TemperatureMonitor()
{
observers = new List<IObserver<Temperature>>();
}
private class Unsubscriber : IDisposable
{
private List<IObserver<Temperature>> _observers;
private IObserver<Temperature> _observer;
public Unsubscriber(List<IObserver<Temperature>> observers, IObserver<Temperature> observer)
{
this._observers = observers;
this._observer = observer;
}
public void Dispose()
{
if (! (_observer == null)) _observers.Remove(_observer);
}
}
public IDisposable Subscribe(IObserver<Temperature> observer)
{
if (! observers.Contains(observer))
observers.Add(observer);
return new Unsubscriber(observers, observer);
}
public void GetTemperature()
{
// Create an array of sample data to mimic a temperature device.
Nullable<Decimal>[] temps = {14.6m, 14.65m, 14.7m, 14.9m, 14.9m, 15.2m, 15.25m, 15.2m,
15.4m, 15.45m, null };
// Store the previous temperature, so notification is only sent after at least .1 change.
Nullable<Decimal> previous = null;
bool start = true;
foreach (var temp in temps) {
System.Threading.Thread.Sleep(2500);
if (temp.HasValue) {
if (start || (Math.Abs(temp.Value - previous.Value) >= 0.1m )) {
Temperature tempData = new Temperature(temp.Value, DateTime.Now);
foreach (var observer in observers)
observer.OnNext(tempData);
previous = temp;
if (start) start = false;
}
}
else {
foreach (var observer in observers.ToArray())
if (observer != null) observer.OnCompleted();
observers.Clear();
break;
}
}
}
}
Imports System.Threading
Imports System.Collections.Generic
Public Class TemperatureMonitor : Implements IObservable(Of Temperature)
Dim observers As List(Of IObserver(Of Temperature))
Public Sub New()
observers = New List(Of IObserver(Of Temperature))
End Sub
Private Class Unsubscriber : Implements IDisposable
Private _observers As List(Of IObserver(Of Temperature))
Private _observer As IObserver(Of Temperature)
Public Sub New(ByVal observers As List(Of IObserver(Of Temperature)), ByVal observer As IObserver(Of Temperature))
Me._observers = observers
Me._observer = observer
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
If _observer IsNot Nothing Then _observers.Remove(_observer)
End Sub
End Class
Public Function Subscribe(ByVal observer As System.IObserver(Of Temperature)) As System.IDisposable Implements System.IObservable(Of Temperature).Subscribe
If Not observers.Contains(observer) Then
observers.Add(observer)
End If
Return New Unsubscriber(observers, observer)
End Function
Public Sub GetTemperature()
' Create an array of sample data to mimic a temperature device.
Dim temps() As Nullable(Of Decimal) = {14.6D, 14.65D, 14.7D, 14.9D, 14.9D, 15.2D, 15.25D, 15.2D,
15.4D, 15.45D, Nothing}
' Store the previous temperature, so notification is only sent after at least .1 change.
Dim previous As Nullable(Of Decimal)
Dim start As Boolean = True
For Each temp In temps
System.Threading.Thread.Sleep(2500)
If temp.HasValue Then
If start OrElse Math.Abs(temp.Value - previous.Value) >= 0.1 Then
Dim tempData As New Temperature(temp.Value, Date.Now)
For Each observer In observers
observer.OnNext(tempData)
Next
previous = temp
If start Then start = False
End If
Else
For Each observer In observers.ToArray()
If observer IsNot Nothing Then observer.OnCompleted()
Next
observers.Clear()
Exit For
End If
Next
End Sub
End Class