Vorgehensweise: Implementieren eines Anbieters
Das Entwurfsmuster „Observer“ erfordert eine Trennung zwischen einem Anbieter, der Daten überwacht und Benachrichtigungen sendet, und mindestens einem Beobachter, der Benachrichtigungen (Rückrufe) vom Anbieter empfängt. In diesem Thema wird das Erstellen eines Anbieters behandelt. In dem verwandten Thema Gewusst wie: Implementieren eines Observers wird erläutert, wie ein Observer erstellt wird.
So erstellen Sie einen Anbieter
Definieren Sie die Daten, die vom Anbieter an Observer gesendet werden. Obwohl nur der Anbieter und die an Observer gesendeten Daten einen einzelnen Typ aufweisen können, werden sie in der Regel von unterschiedlichen Typen dargestellt. Beispiel: In einer Anwendung zur Temperaturüberwachung definiert die
Temperature
-Struktur die Daten, die der Anbieter (dargestellt durch die im nächsten Schritt definierteTemperatureMonitor
-Klasse) überwacht, und die abonnierten Observer.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
Definieren Sie den Datenanbieter, der einen Typ zur Implementierung der System.IObservable<T>-Schnittstelle aufweist. Das generische Typargument des Anbieters ist der Typ, den der Anbieter an Observer sendet. Das folgende Beispiel definiert eine
TemperatureMonitor
-Klasse, die eine erstellte System.IObservable<T>-Implementierung mit einem generischen Typargument vonTemperature
darstellt.using System; using System.Collections.Generic; public class TemperatureMonitor : IObservable<Temperature> {
Imports System.Collections.Generic Public Class TemperatureMonitor : Implements IObservable(Of Temperature)
Stellen Sie fest, wie der Anbieter Verweise auf Observer speichert, damit jeder Observer ggf. benachrichtigt werden kann. In den meisten Fällen wird hierfür ein Auflistungsobjekt wie etwa ein generisches List<T>-Objekt verwendet. Das folgende Beispiel definiert ein privates List<T>-Objekt, das im Klassenkonstruktor
TemperatureMonitor
instanziiert wird.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
Definieren Sie eine IDisposable-Implementierung, die der Anbieter an Abonnenten zurückgeben kann, sodass diese jederzeit den Empfang von Benachrichtigungen beenden können. Im folgenden Beispiel wird eine geschachtelte
Unsubscriber
-Klasse definiert, der bei Instanziierung der Klasse ein Verweis auf die Auflistung der Abonnenten und auf den Abonnenten übergeben wird. Durch diesen Code kann der Abonnent die IDisposable.Dispose-Implementierung des Objekts aufrufen, damit es selbst aus der Abonnentenauflistung entfernt wird.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
Implementieren Sie die IObservable<T>.Subscribe-Methode. Der Methode wird ein Verweis auf die System.IObserver<T>-Schnittstelle übergeben, und sie sollte im Objekt gespeichert werden, das hierfür in Schritt 3 entworfen wurde. Die Methode sollte dann die in Schritt 4 entwickelte IDisposable-Implementierung zurückgeben. Im folgenden Beispiel wird die Implementierung der Subscribe-Methode in der Klasse
TemperatureMonitor
veranschaulicht.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
Benachrichtigen Sie ggf. Observer, indem Sie ihre IObserver<T>.OnNext-, IObserver<T>.OnError- und IObserver<T>.OnCompleted-Implementierungen aufrufen. In einigen Fällen kann ein Anbieter beim Auftreten eines Fehlers nicht die OnError-Methode aufrufen. Beispielsweise simuliert die folgende
GetTemperature
-Methode einen Monitor, der alle fünf Sekunden Temperaturdaten liest und Observer benachrichtigt, wenn sich die Temperatur seit dem letzten Lesevorgang um mindestens 0,1 Grad geändert hat. Wenn das Gerät keine Temperatur meldet (d.h., wenn der Wert null ist), werden Observer durch den Anbieter darüber benachrichtigt, dass die Übertragung abgeschlossen ist. Beachten Sie, dass bei derGetTemperature
-Methode nicht nur die OnCompleted-Methode der einzelnen Observer aufgerufen wird, sondern auch die List<T>-Auflistung gelöscht wird. Dabei ruft der Anbieter nicht die OnError-Methode der jeweiligen Observer auf.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
Beispiel
Das folgende Beispiel enthält den vollständigen Quellcode, um eine IObservable<T>-Implementierung für eine Anwendung zur Temperaturüberwachung zu definieren. Er enthält die Temperature
-Struktur, die die an Observer gesendeten Daten umfasst, und die TemperatureMonitor
-Klasse, die die IObservable<T>-Implementierung darstellt.
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