Bu makalede, .NET C#'taki Observer Design Pattern'i bazı geliştirmelerle birlikte öğreneceksiniz.
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
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.
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:
Bunlar , .NET C#' ta Gözlemci Tasarım Desenini uygulamak için kullanılan soyutlamalardır .
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.
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.
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.
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.
Ş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.
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.
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:
WeatherForecast
sınıfı IObservable<WeatherInfo>
uygulamasını uyguluyor.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.WeatherForecastUnsubscriber
sınıfının yeni bir örneğini döndürüyoruz.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.
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:
IDisposable
uygular.
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:
Unsubscriber<T>
sınıfından miras alınır.
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:
WeatherForecastObserver
sınıfı, IObserver<WeatherInfo>
uygulamasını uyguluyor.OnNext
yönteminde sıcaklığı konsola yazıyoruz.OnCompleted
metodunda konsola “Completed” yazıyoruz.OnError
metodunda konsola “Error” yazıyoruz.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.void Unsubscribe()
yöntemi tanımlanır ve dahili olarak kaydedilen aboneliği iptal etme nesnesinden yararlanır.
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:
Microsoft'un uygulamasını kontrol ettiğimde bazı endişeler buldum. Bu nedenle bazı küçük değişiklikler yapmaya karar verdim.
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:
IExtendedObservable<out T>
arabirimi, IObservable<T>
arabirimini genişletir.IReadOnlyCollection<T> Snapshot
özelliğini tanımladık.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.
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:
Unsubscriber
sınıfı genel bir sınıf değildir.
using System; using System.Collections.Generic; namespace ExtendedObservable { public class WeatherForecastUnsubscriber : Unsubscriber { public WeatherForecastUnsubscriber( Action unsubscribeAction) : base(unsubscribeAction) { } } }
Burada neyi fark edebiliriz:
Unsubscriber<T>
den <T>
kısmını kaldırdık.Action
üstleniyor.
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:
m_WeatherInfoList
listesini döndüren ancak IReadOnlyCollection
olarak döndüren IReadOnlyCollection<WeatherInfo> Snapshot
özelliği dışında hemen hemen aynıdır.withHistory
parametresini kullanan IDisposable Subscribe(IObserver<WeatherInfo> observer, bool withHistory)
yöntemi.
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.
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ı.
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
Bu nedenle bu kütüphaneleri keşfetmenizi ve denemenizi tavsiye ederim. Eminim bazılarını beğeneceksiniz.