paint-brush
.NET C#'ta Gözlemci Tasarım Deseni İçin Temel Bir Kılavuzby@ahmedtarekhasan
3,180
3,180

.NET C#'ta Gözlemci Tasarım Deseni İçin Temel Bir Kılavuz

Ahmed Tarek Hasan19m2023/04/10
Read on Terminal Reader
Read this story w/o Javascript

Bu makalede, bazı geliştirmelerle birlikte.NET C#'taki Gözlemci Tasarım Deseni hakkında bilgi edineceksiniz. Gözlemci tasarım modeli, bir abonenin bir sağlayıcıya kaydolmasını ve sağlayıcıdan bildirim almasını sağlar. Push tabanlı bildirim gerektiren her senaryo için uygundur. Deseni benzersiz kılan şey, onu kullanarak bunu sıkı bir ilişki kurmadan başarabilmenizdir.
featured image - .NET C#'ta Gözlemci Tasarım Deseni İçin Temel Bir Kılavuz
Ahmed Tarek Hasan HackerNoon profile picture
0-item

Bu makalede, .NET C#'taki Observer Design Pattern'i bazı geliştirmelerle birlikte öğreneceksiniz.


Gözlemci Tasarım Modeli Tanımı

Gözlemci Tasarım Deseni, en önemli ve yaygın olarak kullanılan tasarım desenlerinden biridir.


Öncelikle Observer Design Pattern'in resmi tanımını kontrol edelim.


göre Microsoft'un belgeleri :


Gözlemci tasarım modeli, bir abonenin bir sağlayıcıya kaydolmasını ve sağlayıcıdan bildirim almasını sağlar. Push tabanlı bildirim gerektiren her senaryo için uygundur. Desen, bir sağlayıcıyı (özne veya gözlemlenebilir olarak da bilinir) ve sıfır, bir veya daha fazla gözlemciyi tanımlar. Gözlemciler sağlayıcıya kaydolur ve önceden tanımlanmış bir durum, olay veya durum değişikliği meydana geldiğinde sağlayıcı, yöntemlerinden birini çağırarak tüm gözlemcileri otomatik olarak bilgilendirir. Bu yöntem çağrısında sağlayıcı aynı zamanda gözlemcilere mevcut durum bilgisini de sağlayabilir. .NET'te gözlemci tasarım modeli, genel System.IObservable<T> ve System.IObserver<T> arabirimlerinin uygulanmasıyla uygulanır. Genel tür parametresi, bildirim bilgilerini sağlayan türü temsil eder.


Yani yukarıdaki tanımdan şunu anlayabiliriz:

  1. İki partimiz veya modülümüz var.
  2. Sağlanacak bazı bilgi akışını içeren modül. Bu modüle Sağlayıcı (bilgi sağladığı için) veya Konu (bilgiyi dış dünyaya aktardığı için) veya Gözlemlenebilir (dış dünya tarafından gözlemlenebildiği için) adı verilir.
  3. Başka bir yerden gelen bilgi akışıyla ilgilenen modül. Bu modüle Gözlemci adı verilir (bilgiyi gözlemlediği için).

Unsplash'ta Den Harrson'un fotoğrafı

Gözlemci Tasarım Deseninin Avantajları

Artık bildiğimiz gibi Observer Tasarım Modeli , Observable ve Observer modülleri arasındaki ilişkiyi formüle etmektedir. Observer Design Pattern'i benzersiz kılan şey, onu kullanarak bunu sıkı bir bağ kurmadan başarabilmenizdir.


Desenin çalışma şeklini analiz ederek aşağıdakileri bulacaksınız:

  1. Observable, Observer hakkında gereken minimum bilgiyi bilir.
  2. Observer, Observable hakkında gereken minimum bilgiyi bilir.
  3. Ortak bilgi bile somut uygulamalarla değil, soyutlamalarla elde edilir.
  4. Sonuçta her iki modül de kendi işini yapabilir, yalnızca kendi işini yapabilir.

Unsplash'ta Lucas Santos'un fotoğrafı

Kullanılan Soyutlamalar

Bunlar , .NET C#' ta Gözlemci Tasarım Desenini uygulamak için kullanılan soyutlamalardır .



IGözlenebilir<out T>

Bu, herhangi bir Observable'ı temsil eden bir Kovaryant arayüzüdür. .NET'te Variance hakkında daha fazla bilgi edinmek istiyorsanız makaleye göz atabilirsiniz. .NET C#'ta Kovaryans ve Karşıtlık .


Bu arayüzde tanımlanan üyeler şunlardır:


 public IDisposable Subscribe (IObserver<out T> observer);


Bazı Gözlemcilerin bilgi akışıyla ilgilendiğini Gözlemlenebilire bildirmek için Subscribe yöntemi çağrılmalıdır.


Subscribe yöntemi, IDisposable arabirimini uygulayan bir nesneyi döndürür. Bu nesne daha sonra Observer tarafından Observable tarafından sağlanan bilgi akışından çıkmak için kullanılabilir. Bu yapıldıktan sonra Gözlemciye bilgi akışındaki herhangi bir güncelleme hakkında bilgi verilmeyecektir.



IObserver<T> cinsinden

Bu, herhangi bir Observer'ı temsil eden bir Contravariant arayüzüdür. .NET'te Variance hakkında daha fazla bilgi edinmek istiyorsanız makaleye göz atabilirsiniz. .NET C#'ta Kovaryans ve Karşıtlık .


Bu arayüzde tanımlanan üyeler şunlardır:


 public void OnCompleted (); public void OnError (Exception error); public void OnNext (T value);


Observer'a bilgi akışının tamamlandığını ve Observer'ın daha fazla bilgi beklememesi gerektiğini bildirmek için OnCompleted yöntemi Observable tarafından çağrılmalıdır.


Observer'a bir hata oluştuğunu bildirmek için Observable tarafından OnError yöntemi çağrılmalıdır.


OnNext yöntemi, Observer'a yeni bir bilgi parçasının hazır olduğunu ve akışa eklendiğini bildirmek için Observable tarafından çağrılmalıdır.


Unsplash'ta Tadas Sar'ın fotoğrafı

Microsoft'un Uygulaması

Şimdi Microsoft'un Observer Design Pattern'in C#'ta uygulanmasını nasıl önerdiğini görelim. Daha sonra size kendi uyguladığım bazı küçük geliştirmeleri göstereceğim.


Basit bir Hava Tahmini Konsol Uygulaması oluşturacağız. Bu uygulamada WeatherForecast modülümüz (Observable, Provider, Subject) ve WeatherForecastObserver modülümüz (Observer) olacak.


Öyleyse uygulamayı incelemeye başlayalım.



Hava Durumu Bilgisi

 namespace Observable { public class WeatherInfo { internal WeatherInfo(double temperature) { Temperature = temperature; } public double Temperature { get; } } }


Bu, bilgi akışında akacak bilgi parçasını temsil eden varlıktır.



Hava Durumu tahmini

 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecast : IObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IDisposable Subscribe(IObserver<WeatherInfo> observer) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } return new WeatherForecastUnsubscriber(m_Observers, observer); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


Burada neyi fark edebiliriz:

  1. WeatherForecast sınıfı IObservable<WeatherInfo> uygulamasını uyguluyor.
  2. Subscribe yönteminin uygulanmasında Observer'da aktarılanın daha önce kayıtlı olup olmadığını kontrol ediyoruz. Değilse, onu yerel m_Observers gözlemcileri listesine ekliyoruz. Daha sonra yerel m_WeatherInfoList listesindeki tüm WeatherInfo girişlerini tek tek döngüye alıyoruz ve Observer'ın OnNext metodunu çağırarak Observer'ı bu konuda bilgilendiriyoruz.
  3. Son olarak, bilgi akışı aboneliğinden çıkmak için Observer tarafından kullanılacak WeatherForecastUnsubscriber sınıfının yeni bir örneğini döndürüyoruz.
  4. RegisterWeatherInfo yöntemi, ana modülün yeni WeatherInfo kaydedebilmesi için tanımlanır. Gerçek dünyada bu, dahili planlanmış bir API çağrısıyla veya SignalR Hub'a yönelik bir dinleyiciyle veya bilgi kaynağı olarak görev yapacak başka bir şeyle değiştirilebilir.



Aboneliği iptal eden<T>

 using System; using System.Collections.Generic; namespace Observable { public class Unsubscriber<T> : IDisposable { private readonly List<IObserver<T>> m_Observers; private readonly IObserver<T> m_Observer; private bool m_IsDisposed; public Unsubscriber(List<IObserver<T>> observers, IObserver<T> observer) { m_Observers = observers; m_Observer = observer; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing && m_Observers.Contains(m_Observer)) { m_Observers.Remove(m_Observer); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


Burada neyi fark edebiliriz:

  1. Bu, abone olmayanlar için temel sınıftır.
  2. Disposable Design Pattern'i uygulayarak IDisposable uygular.
  3. Yapıcı aracılığıyla, Gözlemcilerin ve kendisi için oluşturulduğu Gözlemcinin tam listesini alır.
  4. İmha ederken, Observer'ın Observer'ların tam listesinde zaten mevcut olup olmadığını kontrol eder. Evet ise listeden çıkarır.



Hava TahminiAboneliği İptal Et

 using System; using System.Collections.Generic; namespace Observable { public class WeatherForecastUnsubscriber : Unsubscriber<WeatherInfo> { public WeatherForecastUnsubscriber( List<IObserver<WeatherInfo>> observers, IObserver<WeatherInfo> observer) : base(observers, observer) { } } }


Burada neyi fark edebiliriz:

  1. Bu, Unsubscriber<T> sınıfından miras alınır.
  2. Özel bir işlem gerçekleşmiyor.



Hava Tahmini Gözlemcisi

 using System; namespace Observable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


Burada neyi fark edebiliriz:

  1. WeatherForecastObserver sınıfı, IObserver<WeatherInfo> uygulamasını uyguluyor.
  2. OnNext yönteminde sıcaklığı konsola yazıyoruz.
  3. OnCompleted metodunda konsola “Completed” yazıyoruz.
  4. OnError metodunda konsola “Error” yazıyoruz.
  5. Ana modülün kayıt sürecini tetiklemesine izin vermek için void Subscribe(WeatherForecast provider) yöntemini tanımladık. Döndürülen aboneliği iptal etme nesnesi, abonelikten çıkma durumunda kullanılmak üzere dahili olarak kaydedilir.
  6. Aynı konsepti kullanarak void Unsubscribe() yöntemi tanımlanır ve dahili olarak kaydedilen aboneliği iptal etme nesnesinden yararlanır.



programı

 using System; namespace Observable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


Burada neyi fark edebiliriz:

  1. Sağlayıcının bir örneğini oluşturduk.
  2. Daha sonra 3 adet bilgi kaydedildi.
  3. Şu ana kadar hiçbir gözlemci tanımlanmadığından konsola hiçbir şeyin kaydedilmemesi gerekmektedir.
  4. Daha sonra gözlemcinin bir örneğini yarattık.
  5. Daha sonra gözlemciyi yayına abone etti.
  6. Şu anda konsolda kayıtlı 3 sıcaklık bulmamız gerekiyor. Çünkü gözlemci abone olduğunda zaten var olan bilgilerden haberdar oluyor ve bizim durumumuzda bunlar 3 parça bilgi.
  7. Daha sonra 2 adet bilgiyi kaydediyoruz.
  8. Böylece konsola kayıtlı 2 mesaj daha alıyoruz.
  9. Daha sonra abonelikten çıkıyoruz.
  10. Daha sonra 1 adet bilgiyi kaydediyoruz.
  11. Ancak gözlemci zaten abonelikten çıktığı için bu bilgi konsola kaydedilmeyecekti.
  12. Daha sonra gözlemci tekrar abone olur.
  13. Daha sonra 1 adet bilgiyi kaydediyoruz.
  14. Yani bu bilgi konsola kaydedilir.


Son olarak, bunu çalıştırmak şu sonuçla sonuçlanmalıdır:


Resim: Ahmed Tarek


Unsplash'ta Bruno Yamazaky'nin fotoğrafı

Genişletilmiş Uygulamam

Microsoft'un uygulamasını kontrol ettiğimde bazı endişeler buldum. Bu nedenle bazı küçük değişiklikler yapmaya karar verdim.


IExtishedObservable<out T>

 using System; using System.Collections.Generic; namespace ExtendedObservable { public interface IExtendedObservable<out T> : IObservable<T> { IReadOnlyCollection<T> Snapshot { get; } IDisposable Subscribe(IObserver<T> observer, bool withHistory); } }


Burada neyi fark edebiliriz:

  1. IExtendedObservable<out T> arabirimi, IObservable<T> arabirimini genişletir.
  2. Kovaryanttır . Bu konuda daha fazla bilgi edinmek istiyorsanız makaleye göz atabilirsiniz. .NET C#'ta Kovaryans ve Karşıtlık .
  3. Diğer modüllerin abone olmak zorunda kalmadan mevcut bilgi girişlerinin anında listesini almasına izin vermek için IReadOnlyCollection<T> Snapshot özelliğini tanımladık.
  4. Ayrıca IDisposable Subscribe(IObserver<T> observer, bool withHistory) yöntemini ekstra bir bool withHistory parametresiyle tanımladık, böylece Observer abone olma anında mevcut bilgi girişleri hakkında bildirim almak isteyip istemediğine karar verebilir.



Aboneliği iptal eden

 using System; namespace ExtendedObservable { public class Unsubscriber : IDisposable { private readonly Action m_UnsubscribeAction; private bool m_IsDisposed; public Unsubscriber(Action unsubscribeAction) { m_UnsubscribeAction = unsubscribeAction; } public void Dispose() { Dispose(true); GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (m_IsDisposed) return; if (disposing) { m_UnsubscribeAction(); } m_IsDisposed = true; } ~Unsubscriber() { Dispose(false); } } }


Burada neyi fark edebiliriz:

  1. Abonelikten Unsubscriber sınıfı genel bir sınıf değildir.
  2. Bunun nedeni, bilgi varlığının türünü bilmek için daha fazla bilgiye ihtiyaç duymamasıdır.
  3. Observer'ların ve kendisi için oluşturulduğu Observer'ın tam listesine erişim sağlamak yerine, yalnızca imha edildiğinde Observable'a bildirimde bulunur ve Observable, kayıt silme işlemini kendi başına gerçekleştirir.
  4. Bu sayede eskisinden daha az iş yapıyor ve sadece işini yapıyor.



Hava TahminiAboneliği İptal Et

 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } }


Burada neyi fark edebiliriz:

  1. Unsubscriber<T> den <T> kısmını kaldırdık.
  2. Ve şimdi yapıcı, elden çıkarma durumunda çağrılacak bir Action üstleniyor.



Hava Durumu tahmini

 using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecast : IExtendedObservable<WeatherInfo> { private readonly List<IObserver<WeatherInfo>> m_Observers; private readonly List<WeatherInfo> m_WeatherInfoList; public WeatherForecast() { m_Observers = new List<IObserver<WeatherInfo>>(); m_WeatherInfoList = new List<WeatherInfo>(); } public IReadOnlyCollection<WeatherInfo> Snapshot => m_WeatherInfoList; public IDisposable Subscribe(IObserver<WeatherInfo> observer) { return Subscribe(observer, false); } public IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) { if (!m_Observers.Contains(observer)) { m_Observers.Add(observer); if (withHistory) { foreach (var item in m_WeatherInfoList) { observer.OnNext(item); } } } return new WeatherForecastUnsubscriber( () => { if (m_Observers.Contains(observer)) { m_Observers.Remove(observer); } }); } public void RegisterWeatherInfo(WeatherInfo weatherInfo) { m_WeatherInfoList.Add(weatherInfo); foreach (var observer in m_Observers) { observer.OnNext(weatherInfo); } } public void ClearWeatherInfo() { m_WeatherInfoList.Clear(); } } }


Burada neyi fark edebiliriz:

  1. Dahili m_WeatherInfoList listesini döndüren ancak IReadOnlyCollection olarak döndüren IReadOnlyCollection<WeatherInfo> Snapshot özelliği dışında hemen hemen aynıdır.
  2. Ve withHistory parametresini kullanan IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory) yöntemi.



Hava Tahmini Gözlemcisi

 using System; namespace ExtendedObservable { public class WeatherForecastObserver : IObserver<WeatherInfo> { private IDisposable m_Unsubscriber; public virtual void Subscribe(WeatherForecast provider) { m_Unsubscriber = provider.Subscribe(this, true); } public virtual void Unsubscribe() { m_Unsubscriber.Dispose(); } public void OnCompleted() { Console.WriteLine("Completed"); } public void OnError(Exception error) { Console.WriteLine("Error"); } public void OnNext(WeatherInfo value) { Console.WriteLine($"Temperature: {value.Temperature}"); } } }


Burada fark edebileceğimiz şey, artık geçmişle Subscribe olmayacağına karar veren Subscribe(WeatherForecast provider) dışında hemen hemen aynı olmasıdır.



programı

 using System; namespace ExtendedObservable { class Program { static void Main(string[] args) { var provider = new WeatherForecast(); provider.RegisterWeatherInfo(new WeatherInfo(1)); provider.RegisterWeatherInfo(new WeatherInfo(2)); provider.RegisterWeatherInfo(new WeatherInfo(3)); var observer = new WeatherForecastObserver(); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(4)); provider.RegisterWeatherInfo(new WeatherInfo(5)); observer.Unsubscribe(); provider.RegisterWeatherInfo(new WeatherInfo(6)); observer.Subscribe(provider); provider.RegisterWeatherInfo(new WeatherInfo(7)); Console.ReadLine(); } } }


Öncekiyle aynı.




Son olarak, bunu çalıştırmak daha önce olduğu gibi aynı sonucu vermelidir:


Resim: Ahmed Tarek


Unsplash'ta Emily Morter'ın fotoğrafı, Ahmed Tarek tarafından düzenlenmiştir

Sıradaki ne

Artık .NET C#'taki Gözlemci Tasarım Deseninin temellerini biliyorsunuz. Ancak bu hikayenin sonu değil.


Yararlı bulabileceğiniz daha harika özellikler ve yetenekler sağlayan IObservable<T> ve IObserver<T> arayüzleri üzerine oluşturulmuş kütüphaneler vardır.


Bu kütüphanelerden biri de .NET için Reaktif Uzantılar (Rx) kütüphane. Eşzamansız programlamayı desteklemek için bir dizi genişletme yönteminden ve LINQ standart dizi operatörlerinden oluşur.


Bu nedenle bu kütüphaneleri keşfetmenizi ve denemenizi tavsiye ederim. Eminim bazılarını beğeneceksiniz.


Burada da yayınlandı.