操作說明:實作提供者
觀察者設計模式需要提供者和一個或多個觀察者之間的分區,其中提供者會監視資料並傳送通知,而觀察者會接收來自提供者的通知 (回呼)。 本主題討論如何建立提供者。 相關主題為操作說明:實作觀察器,討論如何建立觀察者。
建立提供者
定義提供者負責傳送給觀察者的資料。 雖然提供者和它傳送給觀察者的資料可以是單一的型別,但它們通常以不同型別代表。 例如,在溫度監控應用程式中,
Temperature
結構定義的提供者 (在下一步中以TemperatureMonitor
代表) 監視並由觀察者訂閱的資料。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
定義資料提供者,這是實作 System.IObservable<T> 介面的型別。 提供者的泛型型別引數是提供者傳送給觀察者的型別。 下列範例會定義
TemperatureMonitor
類別,這是包含泛型型別引數Temperature
的建構 System.IObservable<T> 實作。using System; using System.Collections.Generic; public class TemperatureMonitor : IObservable<Temperature> {
Imports System.Collections.Generic Public Class TemperatureMonitor : Implements IObservable(Of Temperature)
決定提供者儲存對觀察者的參考之方式,讓每個觀察者在適當時被通知。 大多數情況下,如泛型 List<T> 物件等集合物件用於此用途。 下列範例定義在
TemperatureMonitor
中具現化的私用 List<T> 物件。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
定義提供者可傳回到訂閱者的 IDisposable 實作,讓它們可隨時停止接收通知。 下列範例定義巢狀
Unsubscriber
類別,它在類別初始化時將參考傳遞到訂閱者集合及訂閱者。 此程式碼讓訂閱者能夠呼叫物件的 IDisposable.Dispose 實作,以將自己從訂閱者集合移除。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
實作 IObservable<T>.Subscribe 方法。 該方法傳遞參考到 System.IObserver<T> 介面,並應該儲存在步驟 3 中為該目的而設計的物件中。 這個方法應再傳回在步驟 4 中開發的 IDisposable 實作。 下列範例顯示
TemperatureMonitor
類別中 Subscribe 方法的實作。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
藉由呼叫觀察者的 IObserver<T>.OnNext、IObserver<T>.OnError 和 IObserver<T>.OnCompleted 實作,以適時通知它們。 在某些情況下,當錯誤發生時提供者可能不會呼叫 OnError 方法。 例如,下列
GetTemperature
方法模擬此監視器每五秒鐘讀取一次溫度資料,並通知觀察者自上次讀取之後溫度是否已變更至少 1 度。 如果裝置未報告溫度 (即如果其值為 Null),提供者會通知觀察者傳輸已完成。 請注意,除了呼叫每個觀察者的 OnCompleted 方法,GetTemperature
方法可清除 List<T> 集合。 在此情況下,提供者沒有呼叫其觀察者的 OnError 方法。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
範例
下列範例包含定義 IObservable<T> 溫度監視應用程式實作的完整原始程式碼。 它包含 Temperature
結構,這是傳送給觀察者的資料,以及 TemperatureMonitor
類別,這是 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