如何:实现提供程序

观察程序设计模式要求区分提供程序(监视数据并发送通知)和一个或多个观察程序(通过提供程序接收通知(回调))。 本主题介绍了如何创建提供程序。 相关主题如何:实现观察程序介绍了如何创建观察程序。

创建提供程序的具体步骤

  1. 定义提供程序负责发送给观察程序的数据。 尽管提供程序和发送给观察程序的数据可能是一种类型,但它们通常由不同类型表示。 例如,在温度监视应用中,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
    
  2. 定义数据提供程序,即实现 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)
    
  3. 确定提供程序如何存储对观察程序的引用,以便视情况通知每个观察程序。 最常见的情况是,集合对象(如泛型 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
    
  4. 定义提供程序可以返回给订阅者的 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
    
  5. 实现 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
    
  6. 通过调用 IObserver<T>.OnNextIObserver<T>.OnErrorIObserver<T>.OnCompleted 实现,视情况通知观察程序。 在某些情况下,如果出错,提供程序可能不会调用 OnError 方法。 例如,下面的 GetTemperature 方法模拟监视器,每 5 秒读取一次温度数据,如果温度自从上一次读数以来出现至少 0.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

另请参阅