Share via


방법: 공급자 구현

관찰자 디자인 패턴은 데이터를 모니터링하고 알림을 보내는 공급자와 해당 공급자로부터 알림(콜백)을 받는 하나 이상의 관찰자 간에 구분이 필요합니다. 이 항목에서는 공급자를 만드는 방법을 설명합니다. 관련 항목인 방법: 관찰자 구현에서는 관찰자를 만드는 방법을 설명합니다.

공급자를 만들려면

  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> 인터페이스를 구현하는 형식인 데이터 공급자를 정의합니다. 공급자의 제네릭 형식 인수는 공급자가 관찰자로 보내는 형식입니다. 다음 예제에서는 Temperature의 제네릭 형식 인수를 사용하여 구성된 System.IObservable<T> 구현인 TemperatureMonitor 클래스를 정의합니다.

    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 클래스 생성자에서 인스턴스화되는 private 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>.OnNext, IObserver<T>.OnErrorIObserver<T>.OnCompleted 구현을 호출하여 적절하게 관찰자에게 알립니다. 일부 경우에 오류가 발생하면 공급자가 OnError 메서드를 호출하지 못할 수 있습니다. 예를 들어 다음 GetTemperature 메서드는 5초마다 온도 데이터를 읽는 모니터를 시뮬레이트하고 온도가 이전 값보다 .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 구조체 및 IObservable<T> 구현인 TemperatureMonitor 클래스가 포함됩니다.

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

참고 항목