Procedura: Implementare un provider

Lo schema progettuale degli observer richiede una divisione tra un provider, che monitora i dati e invia le notifiche, e uno o più observer, che ricevono le notifiche (callback) dal provider. Questo argomento mostra come creare un provider. Un argomento correlato, Procedura: Implementare un observer, descrive come creare un observer.

Per creare un provider

  1. Definire i dati del cui invio agli observer è responsabile il provider. Anche se il provider e i dati inviati agli observer possono essere di un unico tipo, sono in genere rappresentati da tipi diversi. Ad esempio, in un'applicazione di monitoraggio della temperatura la struttura Temperature definisce i dati che il provider (rappresentato dalla classe TemperatureMonitor definita nel prossimo passaggio) monitora e che gli observer sottoscrivono.

    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. Definire il provider di dati, che è un tipo che implementa l'interfaccia System.IObservable<T>. L'argomento di tipo generico del provider è il tipo che il provider invia agli observer. L'esempio seguente definisce una classe TemperatureMonitor, che è un'implementazione System.IObservable<T> costruita con un argomento di tipo generico Temperature.

    using System;
    using System.Collections.Generic;
    
    public class TemperatureMonitor : IObservable<Temperature>
    {
    
    Imports System.Collections.Generic
    
    
    Public Class TemperatureMonitor : Implements IObservable(Of Temperature)
    
  3. Determinare in che modo il provider archivierà i riferimenti agli observer, in modo che ogni observer riceva una notifica nei casi appropriati. In genere, a questo scopo viene usato un oggetto raccolta, ad esempio un oggetto List<T> generico. L'esempio seguente definisce un oggetto List<T> privato di cui viene creata un'istanza nel costruttore di 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
    
  4. Definire un'implementazione IDisposable che il provider può restituire ai sottoscrittori in modo che questi possano smettere di ricevere notifiche in qualsiasi momento. L'esempio seguente definisce una classe Unsubscriber annidata alla quale viene passato un riferimento alla raccolta di sottoscrittori e al sottoscrittore quando viene creata un'istanza della classe. Questo codice consente al sottoscrittore di chiamare l'implementazione IDisposable.Dispose dell'oggetto per rimuovere se stesso dalla raccolta di sottoscrittori.

    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. Implementa il metodo IObservable<T>.Subscribe. Al metodo viene passato un riferimento all'interfaccia System.IObserver<T> e il metodo deve essere archiviato nell'oggetto progettato a questo scopo nel passaggio 3. Il metodo deve quindi restituire l'implementazione IDisposable sviluppata nel passaggio 4. L'esempio seguente mostra l'implementazione del metodo Subscribe nella 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
    
  6. Inviare notifiche agli observer nel modo appropriato chiamando le rispettive implementazioni IObserver<T>.OnNext, IObserver<T>.OnError e IObserver<T>.OnCompleted. In alcuni casi, un provider non può chiamare il metodo OnError quando si verifica un errore. Ad esempio, il metodo GetTemperature seguente simula un monitoraggio che legge i dati di temperatura ogni cinque secondi e invia una notifica agli observer se la temperatura è cambiata di almeno 0,1 gradi rispetto alla lettura precedente. Se il dispositivo non segnala alcuna temperatura (ovvero se il valore è null), il provider invia una notifica agli observer indicando che la trasmissione è completa. Si noti che, oltre a chiamare il metodo OnCompleted di ogni observer, il metodo GetTemperature cancella la raccolta List<T>. In questo caso, il provider non effettua alcuna chiamata al metodo OnError dei relativi 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;
          }
       }
    }
    
    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
    

Esempio

L'esempio seguente contiene il codice sorgente completo per la definizione di un'implementazione IObservable<T> per un'applicazione di monitoraggio della temperatura. L'esempio include la struttura Temperature, che corrisponde ai dati inviati agli observer, e la classe TemperatureMonitor, che corrisponde all'implementazione 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

Vedi anche