1. Giriş
Təsəvvür edin ki, sizin bir obyektiniz var, hansı ki bəzi əməliyyatları yerinə yetirir — məsələn, bir düymə və ya bizim Worker. Eyni zamanda çoxlu digər obyektlər var ki, bu əməliyyata reaksiya verməlidir. Əgər Worker sinifində bütün mümkün "listeners" sərt şəkildə yazılarsa, belə kodun dəstəyi həqiqi kabus olacaq: subscriber siyahısında hər hansı dəyişiklik Worker-in özündə dəyişiklik tələb edəcək.
Bu açıq-qapalı prinsipi (OCP) pozur və pis memarlıq praktikasına hesab olunur.
Observer patteni: ümumi fikir
"Observer" (Observer) bu problemi həll edir. O, publisher obyektinə baş verən dəyişikliklər barədə bir çox maraqlı obyektləri xəbərdar etmək imkanı verir, həmin obyektlərin kim olduğunu və nə etdiyini bilmədən. Publisher sadəcə "siqnal" atır, və maraqlananlar istədikləri kimi reaksiya verirlər.
Analogiya: newsletter-ə abunəlik. Redaksiya və ya kanal (publisher) yeni mesaj göndərir, və bütün abunəçilər (observer-lər) onu alır. Redaksiya kiminsə olduğunu bilmir və buna ehtiyac da yoxdur.
Maraqlı fakt: "Observer" o qədər populyardır ki, GoF (band of four) dizayn pattern-lərinə daxildir.
C#-da Observer: events və delegates ilə gerçəkləşdirmə
C#-da "Observer" pattern-i language features — events və delegates — vasitəsilə "qutudan çıxan" şəkildə həyata keçirilir. Event genişlənmə nöqtəsidir (extension point), hansı ki müxtəlif handlers-lər subscribe ola bilərlər. Manual subscriber siyahısını saxlamağa ehtiyac qalmır — language event mexanizmi bunu edir. Aşağıda əvvəlcə "manual" implementasiyaya, sonra events vasitəsilə implementasiyaya baxacağıq.
2. Klassik Observer implementasiyası events-dən istifadə etmədən
Əgər dildə events olmasaydı, necə görünə bilərdi:
// İobsə (Observer) interfeysi
public interface IObserver
{
void Update(string message);
}
// Publisher
public class Worker
{
private List<IObserver> observers = new List<IObserver>();
public void Subscribe(IObserver observer)
{
observers.Add(observer);
}
public void Unsubscribe(IObserver observer)
{
observers.Remove(observer);
}
public void DoWork()
{
Console.WriteLine("Worker işləyir...");
NotifyObservers("İş tamamlandı!");
}
private void NotifyObservers(string message)
{
foreach (var observer in observers)
{
observer.Update(message);
}
}
}
// Konkret observer
public class WorkListener : IObserver
{
public void Update(string message)
{
Console.WriteLine($"
WorkListener mesaj aldı: {message}");
}
}
İnitializasiya:
var worker = new Worker();
var listener = new WorkListener();
worker.Subscribe(listener);
worker.DoWork();
Qeyd: Burada subscriber siyahısı (List<IObserver> observers) əllə idarə olunur, və subscribe/unsubscribe açıq metodlardır (Subscribe/Unsubscribe).
3. Events və delegates — C# stilində yüksək səviyyəli Observer
Eyni şeyi events ilə daha sadə və elegant şəkildə reallaşdıra bilərik. Bu C#-ın Observer tərzidir:
public class Worker
{
public event EventHandler<WorkCompletedEventArgs>? WorkCompleted;
public void DoWork()
{
Console.WriteLine("Worker işləyir...");
OnWorkCompleted("İş tamamlandı!");
}
protected virtual void OnWorkCompleted(string message)
{
WorkCompleted?.Invoke(this, new WorkCompletedEventArgs { Message = message });
}
}
public class WorkCompletedEventArgs : EventArgs
{
public string Message { get; set; }
}
public class WorkListener
{
public void OnWorkCompleted(object? sender, WorkCompletedEventArgs e)
{
Console.WriteLine($"WorkListener mesaj aldı: {e.Message}");
}
}
// Subscribing:
var worker = new Worker();
var listener = new WorkListener();
worker.WorkCompleted += listener.OnWorkCompleted;
worker.DoWork();
Bu yanaşmanın üstünlükləri:
- Subscriber siyahısını əllə saxlamağa ehtiyac yoxdur.
- Events-in bütün imkanları mövcuddur: multiple subscription, unsubscribe, lambdalar.
- Təhlükəsizlik təmin edilir: yalnız publisher event-i invoke edə bilər.
- Loose coupling: publisher listeners haqqında heç nə bilmir.
4. "Observer"un tətbiqimizə inteqrasiyası
Gəlin Observer pattenini konsol tətbiqimizə inteqrasiya edək. Farz edək ki, Worker-in istənilən sayda handlerləri var, hansılar ki işi tamamlandıqda müxtəlif şəkildə reaksiya verəcək: kimsə console-a yazacaq, kimsə tamamlanmış işləri sayacaq, kimsə isə "Şef! Hər şey hazırdır!" tipli e-mail göndərəcək.
Kodu əlavə nümunələrlə genişləndirmək
// İkinci listener — counter
public class WorkCounter
{
public int Count { get; private set; }
public void OnWorkCompleted(object? sender, WorkCompletedEventArgs e)
{
Count++;
Console.WriteLine($"İş qeyd olundu. Cəmi: {Count} tamamlandı.");
}
}
// Obyektləri yaradırıq
var worker = new Worker();
var listener = new WorkListener();
var counter = new WorkCounter();
// Hər ikisini subscribe edirik
worker.WorkCompleted += listener.OnWorkCompleted;
worker.WorkCompleted += counter.OnWorkCompleted;
// Bir neçə işin imitasiyası
worker.DoWork();
worker.DoWork();
// Output:
// Worker işləyir...
// WorkListener mesaj aldı: İş tamamlandı!
// İş qeyd olundu. Cəmi: 1 tamamlandı.
// Worker işləyir...
// WorkListener mesaj aldı: İş tamamlandı!
// İş qeyd olundu. Cəmi: 2 tamamlandı.
Beləliklə, siz lazım olduqca observer-lər əlavə edirsiniz, Worker sinifində bir sətrə belə toxunmursunuz. Worker sabit qalır, sistemin davranışı isə subscriber-lər vasitəsilə genişlənir.
5. Faydalı nüanslar
Real nümunə: Observer interfeyslərdə və GUI-də
Observer pattern GUI framework-lərin əsasıdır. Windows Forms və ya WPF-də düymənin basılması Click event-i yaradır. Siz handler-lər yazırsınız (observer-lər), hansılar ki bu event-ə reaksiya verəcək — Button sinifi və .NET kitabxanası sizin subscriber-ləriniz barədə heç nə bilməlidir.
// WPF və ya WinForms (təxminən)
myButton.Click += (s, e) => MessageBox.Show("İstifadəçi düyməni sıxdı!");
Observer real layihələrdə
- UI (kliklərə, dəyişikliklərə, timer-ə reaksiya və s.).
- Notification və event sistemləri.
- Plugins üçün extensible sistemlər (core event yaradır, extensions subscribe olur).
- Distributed sistemlər və oyun mühərrikləri (loosely coupled reactive chains).
Qısacası, əgər genişlənə bilən sistem yaratmaq lazımdırsa, bir komponent digərinin dəyişikliklərinə reaksiya verə bilər — Observer must have!
7. Xüsusiyyətlər, tipik səhvlər və onların qarşısının alınması
Observer istifadə edərkən potensial problemlər
Memory leak-lər. Əgər subscriber event-ə subscribe edib, amma unsubscribe etməyibsə (xüsusilə uzunömürlü obyektlərdə), garbage collector həmin subscriber-i azad edə bilməz, çünki publisher hələ də ona delegate vasitəsilə referans saxlayır. Bu kritik ola bilər, əgər subscriber artıq lazım deyil, amma publisher yaşamaya davam edir.
Duplicate subscription. Eyni handler iki dəfə subscribe olunarsa, o iki dəfə çağırılacaq — əməliyyatların duplication-u və gözlənilməz effektlər yaranacaq.
Handler-lərdə istisnalar. Əgər handlers-dən biri exception atsa, növbəti subscriber-lərin işinə müdaxilə ola bilər. Handler-lərin dayanıqlılığını planlayın və lazım olduqda onları try-catch ilə əhatə edin ki, digər subscriber-lər də işləsin.
Yayılmış leak sxemi
flowchart LR
Publisher["Publisher
(Worker)"] -- event --> ObserverA["Listener A (Yaşayır!)"]
Publisher -- event --> ObserverB["Listener B (Leak: unsubscribe unutdular)"]
GO TO FULL VERSION