Gözlemci tasarım deseni

Gözlemci tasarım düzeni, abonenin sağlayıcıya kaydolmasını ve sağlayıcıdan bildirim almasını sağlar. Anında iletme tabanlı bildirim gerektiren tüm senaryolar için uygundur. Desen bir sağlayıcı (konu veya gözlemlenebilir olarak da bilinir) ve sıfır, bir veya daha fazla gözlemci tanımlar. Gözlemciler sağlayıcıya kaydolup önceden tanımlanmış bir koşul, olay veya durum değişikliği gerçekleştiğinde, sağlayıcı bir temsilci çağırarak tüm gözlemcilere otomatik olarak bildirimde bulunur. Bu yöntem çağrısında sağlayıcı, gözlemcilere geçerli durum bilgilerini de sağlayabilir. .NET'te, gözlemci tasarım deseni genel System.IObservable<T> ve System.IObserver<T> arabirimler uygulanarak uygulanır. Genel tür parametresi, bildirim bilgilerini sağlayan türü temsil eder.

Desen ne zaman uygulanır?

Gözlemci tasarım deseni, veri kaynağı (iş mantığı) katmanı ve kullanıcı arabirimi (görüntüleme) katmanı gibi iki farklı bileşen veya uygulama katmanı arasında temiz bir ayrımı desteklediğinden dağıtılmış anında iletme tabanlı bildirimler için uygundur. Düzen, bir sağlayıcı istemcilerine geçerli bilgileri sağlamak için geri çağırmaları kullandığında uygulanabilir.

Deseni uygulamak için aşağıdaki ayrıntıları sağlamanız gerekir:

  • Gözlemcilere bildirim gönderen nesne olan sağlayıcı veya konu. Sağlayıcı, arabirimini uygulayan IObservable<T> bir sınıf veya yapıdır. Sağlayıcının, IObservable<T>.Subscribesağlayıcıdan bildirim almak isteyen gözlemciler tarafından çağrılan tek bir yöntemi uygulaması gerekir.

  • Bir sağlayıcıdan bildirim alan bir nesne olan gözlemci. Gözlemci, arabirimini uygulayan IObserver<T> bir sınıf veya yapıdır. Gözlemcinin üç yöntem uygulaması gerekir ve bunların tümü sağlayıcı tarafından çağrılır:

  • Sağlayıcının gözlemcileri izlemesine olanak tanıyan bir mekanizma. Genellikle sağlayıcı, bildirimlere abone olan uygulamalara başvuruları tutmak için IObserver<T> nesne gibi bir System.Collections.Generic.List<T> kapsayıcı nesnesi kullanır. Bu amaçla bir depolama kapsayıcısı kullanmak, sağlayıcının sıfırdan sınırsız sayıda gözlemciye kadar işlemesini sağlar. Gözlemcilerin bildirim alma sırası tanımlanmamıştır; sağlayıcı, siparişi belirlemek için herhangi bir yöntemi kullanabilir.

  • IDisposable Bildirim tamamlandığında sağlayıcının gözlemcileri kaldırmasını sağlayan bir uygulama. Gözlemciler yönteminden uygulamaya bir başvuru IDisposable alır, böylece sağlayıcı bildirim göndermeyi bitirmeden Subscribe önce aboneliği kaldırmak için yöntemini de çağırabilir IDisposable.Dispose .

  • Sağlayıcının gözlemcilerine gönderdiği verileri içeren bir nesne. Bu nesnenin türü ve IObserver<T> arabirimlerinin genel tür parametresine IObservable<T> karşılık gelir. Bu nesne uygulamayla IObservable<T> aynı olsa da, en yaygın olarak ayrı bir tür olur.

Not

Gözlemci tasarım desenini uygulamaya ek olarak ve IObserver<T> arabirimleri kullanılarak IObservable<T> oluşturulan kitaplıkları keşfetmek ilginizi çekebilir. Örneğin, .NET (Rx) için Reaktif Uzantılar, zaman uyumsuz programlamayı desteklemek için bir dizi uzantı yönteminden ve LINQ standart dizi işleçlerinden oluşur.

Deseni uygulama

Aşağıdaki örnek, bir havaalanı bagaj talebi bilgi sistemi uygulamak için gözlemci tasarım desenini kullanır. Sınıf BaggageInfo , gelen uçuşlar ve her uçuştan gelen bagajların teslim alınabileceği döngüler hakkında bilgi sağlar. Aşağıdaki örnekte gösterilmiştir.

namespace Observables.Example;

public readonly record struct BaggageInfo(
    int FlightNumber,
    string From,
    int Carousel);
Public Class BaggageInfo
    Private flightNo As Integer
    Private origin As String
    Private location As Integer

    Friend Sub New(ByVal flight As Integer, ByVal from As String, ByVal carousel As Integer)
        Me.flightNo = flight
        Me.origin = from
        Me.location = carousel
    End Sub

    Public ReadOnly Property FlightNumber As Integer
        Get
            Return Me.flightNo
        End Get
    End Property

    Public ReadOnly Property From As String
        Get
            Return Me.origin
        End Get
    End Property

    Public ReadOnly Property Carousel As Integer
        Get
            Return Me.location
        End Get
    End Property
End Class

Sınıf BaggageHandler , gelen uçuşlar ve bagaj talep döngüleri hakkında bilgi almaktan sorumludur. Dahili olarak iki koleksiyon tutar:

  • _observers: Güncelleştirilmiş bilgileri gözlemleyen istemci koleksiyonu.
  • _flights: Uçuş koleksiyonu ve bunların atanmış döngüleri.

Sınıfın BaggageHandler kaynak kodu aşağıdaki örnekte gösterilmiştir.

namespace Observables.Example;

public sealed class BaggageHandler : IObservable<BaggageInfo>
{
    private readonly HashSet<IObserver<BaggageInfo>> _observers = new();
    private readonly HashSet<BaggageInfo> _flights = new();

    public IDisposable Subscribe(IObserver<BaggageInfo> observer)
    {
        // Check whether observer is already registered. If not, add it.
        if (_observers.Add(observer))
        {
            // Provide observer with existing data.
            foreach (BaggageInfo item in _flights)
            {
                observer.OnNext(item);
            }
        }

        return new Unsubscriber<BaggageInfo>(_observers, observer);
    }

    // Called to indicate all baggage is now unloaded.
    public void BaggageStatus(int flightNumber) =>
        BaggageStatus(flightNumber, string.Empty, 0);

    public void BaggageStatus(int flightNumber, string from, int carousel)
    {
        var info = new BaggageInfo(flightNumber, from, carousel);

        // Carousel is assigned, so add new info object to list.
        if (carousel > 0 && _flights.Add(info))
        {
            foreach (IObserver<BaggageInfo> observer in _observers)
            {
                observer.OnNext(info);
            }
        }
        else if (carousel is 0)
        {
            // Baggage claim for flight is done.
            if (_flights.RemoveWhere(
                flight => flight.FlightNumber == info.FlightNumber) > 0)
            {
                foreach (IObserver<BaggageInfo> observer in _observers)
                {
                    observer.OnNext(info);
                }
            }
        }
    }

    public void LastBaggageClaimed()
    {
        foreach (IObserver<BaggageInfo> observer in _observers)
        {
            observer.OnCompleted();
        }

        _observers.Clear();
    }
}
Public Class BaggageHandler : Implements IObservable(Of BaggageInfo)

    Private observers As List(Of IObserver(Of BaggageInfo))
    Private flights As List(Of BaggageInfo)

    Public Sub New()
        observers = New List(Of IObserver(Of BaggageInfo))
        flights = New List(Of BaggageInfo)
    End Sub

    Public Function Subscribe(ByVal observer As IObserver(Of BaggageInfo)) As IDisposable _
                    Implements IObservable(Of BaggageInfo).Subscribe
        ' Check whether observer is already registered. If not, add it
        If Not observers.Contains(observer) Then
            observers.Add(observer)
            ' Provide observer with existing data.
            For Each item In flights
                observer.OnNext(item)
            Next
        End If
        Return New Unsubscriber(Of BaggageInfo)(observers, observer)
    End Function

    ' Called to indicate all baggage is now unloaded.
    Public Sub BaggageStatus(ByVal flightNo As Integer)
        BaggageStatus(flightNo, String.Empty, 0)
    End Sub

    Public Sub BaggageStatus(ByVal flightNo As Integer, ByVal from As String, ByVal carousel As Integer)
        Dim info As New BaggageInfo(flightNo, from, carousel)

        ' Carousel is assigned, so add new info object to list.
        If carousel > 0 And Not flights.Contains(info) Then
            flights.Add(info)
            For Each observer In observers
                observer.OnNext(info)
            Next
        ElseIf carousel = 0 Then
            ' Baggage claim for flight is done
            Dim flightsToRemove As New List(Of BaggageInfo)
            For Each flight In flights
                If info.FlightNumber = flight.FlightNumber Then
                    flightsToRemove.Add(flight)
                    For Each observer In observers
                        observer.OnNext(info)
                    Next
                End If
            Next
            For Each flightToRemove In flightsToRemove
                flights.Remove(flightToRemove)
            Next
            flightsToRemove.Clear()
        End If
    End Sub

    Public Sub LastBaggageClaimed()
        For Each observer In observers
            observer.OnCompleted()
        Next
        observers.Clear()
    End Sub
End Class

Güncelleştirilmiş bilgileri almak isteyen istemciler yöntemini çağırır BaggageHandler.Subscribe . İstemci daha önce bildirimlere abone olmadıysa, istemcinin IObserver<T> uygulamasına başvuru koleksiyona _observers eklenir.

Aşırı yüklenmiş BaggageHandler.BaggageStatus yöntem, bir uçuştan gelen bagajların boşaltıldığını veya artık boşaltılmadığını belirtmek için çağrılabilir. İlk durumda, yöntem bir uçuş numarası, uçuşun geldiği havaalanı ve bagajların boşaltıldığı döngü geçirilir. İkinci durumda, yöntem yalnızca bir uçuş numarası geçirilir. Boşaltılmakta olan bagajlar için yöntem, yönteme geçirilen bilgilerin koleksiyonda _flights mevcut olup olmadığını BaggageInfo denetler. Ekleyemezse, yöntemi bilgileri ekler ve her gözlemcinin OnNext yöntemini çağırır. Bagajları kaldırılmayan uçuşlar için yöntem, söz konusu uçuşla ilgili bilgilerin koleksiyonda _flights depolanıp depolanmadığını denetler. Bu durumda, yöntemi her gözlemcinin OnNext yöntemini çağırır ve nesnesini koleksiyondan _flights kaldırırBaggageInfo.

Günün son uçuşu indiğinde ve bagajı işlendiğinde, BaggageHandler.LastBaggageClaimed yöntem çağrılır. Bu yöntem, tüm bildirimlerin tamamlandığını belirtmek için her gözlemcinin OnCompleted yöntemini çağırır ve ardından koleksiyonu temizler _observers .

Sağlayıcının Subscribe yöntemi, gözlemcilerin yöntem çağrılmadan önce bildirim almayı durdurmasını OnCompleted sağlayan bir IDisposable uygulama döndürür. Bu Unsubscriber(Of BaggageInfo) sınıfın kaynak kodu aşağıdaki örnekte gösterilmiştir. sınıfı yönteminde BaggageHandler.Subscribe örneklendiğinde, koleksiyona bir başvuru ve koleksiyona _observers eklenen gözlemciye bir başvuru geçirilir. Bu başvurular yerel değişkenlere atanır. Nesnenin Dispose yöntemi çağrıldığında, gözlemcinin koleksiyonda _observers hala var olup olmadığını denetler ve varsa, gözlemciyi kaldırır.

namespace Observables.Example;

internal sealed class Unsubscriber<BaggageInfo> : IDisposable
{
    private readonly ISet<IObserver<BaggageInfo>> _observers;
    private readonly IObserver<BaggageInfo> _observer;

    internal Unsubscriber(
        ISet<IObserver<BaggageInfo>> observers,
        IObserver<BaggageInfo> observer) => (_observers, _observer) = (observers, observer);

    public void Dispose() => _observers.Remove(_observer);
}
Friend Class Unsubscriber(Of BaggageInfo) : Implements IDisposable
    Private _observers As List(Of IObserver(Of BaggageInfo))
    Private _observer As IObserver(Of BaggageInfo)

    Friend Sub New(ByVal observers As List(Of IObserver(Of BaggageInfo)), ByVal observer As IObserver(Of BaggageInfo))
        Me._observers = observers
        Me._observer = observer
    End Sub

    Public Sub Dispose() Implements IDisposable.Dispose
        If _observers.Contains(_observer) Then
            _observers.Remove(_observer)
        End If
    End Sub
End Class

Aşağıdaki örnek, bagaj talep bilgilerini görüntüleyen bir temel sınıf olan adlı ArrivalsMonitorbir IObserver<T> uygulama sağlar. Bilgiler, kaynak şehir adıyla alfabetik olarak görüntülenir. yöntemleri ArrivalsMonitor (Visual Basic'te) veya virtual (C# dilinde) olarak overridable işaretlenir, bu nedenle türetilmiş bir sınıfta geçersiz kılınabilir.

namespace Observables.Example;

public class ArrivalsMonitor : IObserver<BaggageInfo>
{
    private readonly string _name;
    private readonly List<string> _flights = new();
    private readonly string _format = "{0,-20} {1,5}  {2, 3}";
    private IDisposable? _cancellation;    

    public ArrivalsMonitor(string name)
    {
        ArgumentException.ThrowIfNullOrEmpty(name);
        _name = name;
    }

    public virtual void Subscribe(BaggageHandler provider) =>
        _cancellation = provider.Subscribe(this);

    public virtual void Unsubscribe()
    {
        _cancellation?.Dispose();
        _flights.Clear();
    }

    public virtual void OnCompleted() => _flights.Clear();

    // No implementation needed: Method is not called by the BaggageHandler class.
    public virtual void OnError(Exception e)
    {
        // No implementation.
    }

    // Update information.
    public virtual void OnNext(BaggageInfo info)
    {
        bool updated = false;

        // Flight has unloaded its baggage; remove from the monitor.
        if (info.Carousel is 0)
        {
            string flightNumber = string.Format("{0,5}", info.FlightNumber);
            for (int index = _flights.Count - 1; index >= 0; index--)
            {
                string flightInfo = _flights[index];
                if (flightInfo.Substring(21, 5).Equals(flightNumber))
                {
                    updated = true;
                    _flights.RemoveAt(index);
                }
            }
        }
        else
        {
            // Add flight if it doesn't exist in the collection.
            string flightInfo = string.Format(_format, info.From, info.FlightNumber, info.Carousel);
            if (_flights.Contains(flightInfo) is false)
            {
                _flights.Add(flightInfo);
                updated = true;
            }
        }

        if (updated)
        {
            _flights.Sort();
            Console.WriteLine($"Arrivals information from {_name}");
            foreach (string flightInfo in _flights)
            {
                Console.WriteLine(flightInfo);
            }

            Console.WriteLine();
        }
    }
}
Public Class ArrivalsMonitor : Implements IObserver(Of BaggageInfo)
    Private name As String
    Private flightInfos As New List(Of String)
    Private cancellation As IDisposable
    Private fmt As String = "{0,-20} {1,5}  {2, 3}"

    Public Sub New(ByVal name As String)
        If String.IsNullOrEmpty(name) Then Throw New ArgumentNullException("The observer must be assigned a name.")

        Me.name = name
    End Sub

    Public Overridable Sub Subscribe(ByVal provider As BaggageHandler)
        cancellation = provider.Subscribe(Me)
    End Sub

    Public Overridable Sub Unsubscribe()
        cancellation.Dispose()
        flightInfos.Clear()
    End Sub

    Public Overridable Sub OnCompleted() Implements System.IObserver(Of BaggageInfo).OnCompleted
        flightInfos.Clear()
    End Sub

    ' No implementation needed: Method is not called by the BaggageHandler class.
    Public Overridable Sub OnError(ByVal e As System.Exception) Implements System.IObserver(Of BaggageInfo).OnError
        ' No implementation.
    End Sub

    ' Update information.
    Public Overridable Sub OnNext(ByVal info As BaggageInfo) Implements System.IObserver(Of BaggageInfo).OnNext
        Dim updated As Boolean = False

        ' Flight has unloaded its baggage; remove from the monitor.
        If info.Carousel = 0 Then
            Dim flightsToRemove As New List(Of String)
            Dim flightNo As String = String.Format("{0,5}", info.FlightNumber)
            For Each flightInfo In flightInfos
                If flightInfo.Substring(21, 5).Equals(flightNo) Then
                    flightsToRemove.Add(flightInfo)
                    updated = True
                End If
            Next
            For Each flightToRemove In flightsToRemove
                flightInfos.Remove(flightToRemove)
            Next
            flightsToRemove.Clear()
        Else
            ' Add flight if it does not exist in the collection.
            Dim flightInfo As String = String.Format(fmt, info.From, info.FlightNumber, info.Carousel)
            If Not flightInfos.Contains(flightInfo) Then
                flightInfos.Add(flightInfo)
                updated = True
            End If
        End If
        If updated Then
            flightInfos.Sort()
            Console.WriteLine("Arrivals information from {0}", Me.name)
            For Each flightInfo In flightInfos
                Console.WriteLine(flightInfo)
            Next
            Console.WriteLine()
        End If
    End Sub
End Class

ArrivalsMonitor sınıfı ve Unsubscribe yöntemlerini içerirSubscribe. yöntemi, Subscribe sınıfının çağrısı tarafından döndürülen uygulamayı özel bir değişkene kaydetmesini IDisposableSubscribe sağlar. yöntemi, Unsubscribe sınıfının sağlayıcının Dispose uygulamasını çağırarak bildirim aboneliğini kaldırmasını sağlar. ArrivalsMonitorayrıca , OnErrorve OnCompleted yöntemlerinin OnNextuygulamalarını sağlar. OnNext Yalnızca uygulama önemli miktarda kod içerir. Yöntem, varış uçuşları için çıkış noktaları ve bagajlarının bulunduğu döngüler hakkında bilgi sağlayan özel, sıralanmış, genel List<T> bir nesneyle çalışır. BaggageHandler Sınıf yeni bir uçuş gelişini bildirirse, OnNext yöntem uygulaması bu uçuşla ilgili bilgileri listeye ekler. Sınıf, BaggageHandler uçuşun bagajının boşaltıldığını bildirirse, OnNext yöntem bu uçuşu listeden kaldırır. Her değişiklik yapıldığında, liste sıralanır ve konsolda görüntülenir.

Aşağıdaki örnek, sınıfını ve sınıfın BaggageHandler iki örneğini oluşturan uygulama giriş noktasını içerir ve gelen uçuşlarla ArrivalsMonitor ilgili bilgileri eklemek ve kaldırmak için yöntemini kullanır BaggageHandler.BaggageStatus . Her durumda, gözlemciler güncelleştirmeleri alır ve bagaj talep bilgilerini doğru bir şekilde görüntüler.

using Observables.Example;

BaggageHandler provider = new();
ArrivalsMonitor observer1 = new("BaggageClaimMonitor1");
ArrivalsMonitor observer2 = new("SecurityExit");

provider.BaggageStatus(712, "Detroit", 3);
observer1.Subscribe(provider);

provider.BaggageStatus(712, "Kalamazoo", 3);
provider.BaggageStatus(400, "New York-Kennedy", 1);
provider.BaggageStatus(712, "Detroit", 3);
observer2.Subscribe(provider);

provider.BaggageStatus(511, "San Francisco", 2);
provider.BaggageStatus(712);
observer2.Unsubscribe();

provider.BaggageStatus(400);
provider.LastBaggageClaimed();

// Sample output:
//   Arrivals information from BaggageClaimMonitor1
//   Detroit                712    3
//   
//   Arrivals information from BaggageClaimMonitor1
//   Detroit                712    3
//   Kalamazoo              712    3
//   
//   Arrivals information from BaggageClaimMonitor1
//   Detroit                712    3
//   Kalamazoo              712    3
//   New York-Kennedy       400    1
//   
//   Arrivals information from SecurityExit
//   Detroit                712    3
//   
//   Arrivals information from SecurityExit
//   Detroit                712    3
//   Kalamazoo              712    3
//   
//   Arrivals information from SecurityExit
//   Detroit                712    3
//   Kalamazoo              712    3
//   New York-Kennedy       400    1
//   
//   Arrivals information from BaggageClaimMonitor1
//   Detroit                712    3
//   Kalamazoo              712    3
//   New York-Kennedy       400    1
//   San Francisco          511    2
//   
//   Arrivals information from SecurityExit
//   Detroit                712    3
//   Kalamazoo              712    3
//   New York-Kennedy       400    1
//   San Francisco          511    2
//   
//   Arrivals information from BaggageClaimMonitor1
//   New York-Kennedy       400    1
//   San Francisco          511    2
//   
//   Arrivals information from SecurityExit
//   New York-Kennedy       400    1
//   San Francisco          511    2
//   
//   Arrivals information from BaggageClaimMonitor1
//   San Francisco          511    2
Module Example
    Public Sub Main()
        Dim provider As New BaggageHandler()
        Dim observer1 As New ArrivalsMonitor("BaggageClaimMonitor1")
        Dim observer2 As New ArrivalsMonitor("SecurityExit")

        provider.BaggageStatus(712, "Detroit", 3)
        observer1.Subscribe(provider)
        provider.BaggageStatus(712, "Kalamazoo", 3)
        provider.BaggageStatus(400, "New York-Kennedy", 1)
        provider.BaggageStatus(712, "Detroit", 3)
        observer2.Subscribe(provider)
        provider.BaggageStatus(511, "San Francisco", 2)
        provider.BaggageStatus(712)
        observer2.Unsubscribe()
        provider.BaggageStatus(400)
        provider.LastBaggageClaimed()
    End Sub
End Module
' The example displays the following output:
'      Arrivals information from BaggageClaimMonitor1
'      Detroit                712    3
'
'      Arrivals information from BaggageClaimMonitor1
'      Detroit                712    3
'      Kalamazoo              712    3
'
'      Arrivals information from BaggageClaimMonitor1
'      Detroit                712    3
'      Kalamazoo              712    3
'      New York-Kennedy       400    1
'
'      Arrivals information from SecurityExit
'      Detroit                712    3
'
'      Arrivals information from SecurityExit
'      Detroit                712    3
'      Kalamazoo              712    3
'
'      Arrivals information from SecurityExit
'      Detroit                712    3
'      Kalamazoo              712    3
'      New York-Kennedy       400    1
'
'      Arrivals information from BaggageClaimMonitor1
'      Detroit                712    3
'      Kalamazoo              712    3
'      New York-Kennedy       400    1
'      San Francisco          511    2
'
'      Arrivals information from SecurityExit
'      Detroit                712    3
'      Kalamazoo              712    3
'      New York-Kennedy       400    1
'      San Francisco          511    2
'
'      Arrivals information from BaggageClaimMonitor1
'      New York-Kennedy       400    1
'      San Francisco          511    2
'
'      Arrivals information from SecurityExit
'      New York-Kennedy       400    1
'      San Francisco          511    2
'
'      Arrivals information from BaggageClaimMonitor1
'      San Francisco          511    2
Ünvan Açıklama
Gözlemci Tasarım Deseni En İyi Yöntemleri Gözlemci tasarım desenini uygulayan uygulamalar geliştirirken benimsenecek en iyi yöntemleri açıklar.
Nasıl yapılır: Sağlayıcıyı Uygulama Sıcaklık izleme uygulaması için bir sağlayıcının adım adım uygulamasını sağlar.
Nasıl yapılır: Gözlemci Uygulama Bir sıcaklık izleme uygulaması için bir gözlemcinin adım adım uygulamasını sağlar.