Behavioral — Davranışsal Desenler
Observer
Gözlemci (Yayın-Abone) Deseni
Amaç: Bir nesnenin durumu değiştiğinde, ona bağlı (abone olmuş) tüm nesnelerin otomatik olarak haberdar edilmesini sağlamak.
Basit Anlatım
Teknik terim yok — günlük hayattan bir benzetme
Bir YouTube kanalına abone olup bildirim zilini açtığınızı düşünün. Kanal yeni bir video yüklediğinde size otomatik olarak bildirim gelir. Siz her gün kanala girip "Yeni video var mı?" diye kontrol etmek zorunda kalmazsınız.
Kanal sahibi de abonelerini tek tek tanımaz. Sadece "Yeni video yayınlandı" der; sistem bunu tüm abonelere iletir. İstediğiniz zaman abonelikten çıkabilirsiniz, kanal bundan etkilenmez.
Gazete aboneliği
Gazeteye abone olursanız her sabah kapınıza gelir. Bayiye gidip sormanıza gerek yoktur. Taşınırsanız ya da istemezseniz aboneliği iptal edersiniz.
Teknik Anlamı
Bir yazılımcının bakış açısıyla
Observer, nesneler arasında bire-çok bir bağımlılık tanımlar. Bir nesne (Subject / Publisher) durumu değiştiğinde, kayıtlı tüm Observer / Subscriber nesnelerini bilgilendirir. Subject, observer'ların somut tiplerini bilmez; sadece ortak bir arayüzü bilir.
Katılımcılar
- Subject: Observer ekleme/çıkarma ve bildirim yapma sorumluluğu.
- Observer: Bildirim alındığında çağrılacak metodu tanımlar.
- ConcreteObserver: Bildirime kendine özgü şekilde tepki verir.
Polling'e karşı Push
Observer olmadan ilgilenen taraflar sürekli "değişti mi?" diye sorar (polling): gereksiz yük ve gecikme. Observer ile değişiklik anında itilir (push).
.NET'te Observer
- C# event ve delegate'leri bu desenin dile gömülü halidir:
event EventHandler<T>,+=ile abone ol,-=ile çık. IObservable<T>/IObserver<T>arayüzleri ve Reactive Extensions (Rx.NET).INotifyPropertyChanged(WPF/MAUI veri bağlama), domain event'ler, mesaj kuyrukları.
Avantajlar
- Subject ile observer'lar arasında gevşek bağlılık.
- Yeni observer eklemek subject'i değiştirmez (Open/Closed).
- İlişkiler çalışma zamanında kurulup kaldırılabilir.
Dezavantajlar
- Bildirim sırası garanti değildir.
- Aboneliği kaldırmayı unutmak bellek sızıntısına yol açar (subject, observer'a referans tutmaya devam eder).
C# ile Uygulama
Adım adım, çalışan kod örneği
Senaryo: Bir hisse senedi fiyatı değiştiğinde ekran, uyarı servisi ve loglama sistemi haberdar olmalı. Önce klasik arayüz yaklaşımını, sonra C# event ile aynı işi göreceğiz.
Adım 1 Klasik yaklaşım: Observer arayüzü ve Subject
public interface IStockObserver
{
void Update(string symbol, decimal oldPrice, decimal newPrice);
}
public class Stock
{
private readonly List<IStockObserver> _observers = new();
private decimal _price;
public string Symbol { get; }
public Stock(string symbol, decimal price) { Symbol = symbol; _price = price; }
public void Subscribe(IStockObserver o) => _observers.Add(o);
public void Unsubscribe(IStockObserver o) => _observers.Remove(o);
public decimal Price
{
get => _price;
set
{
if (value == _price) return;
var old = _price;
_price = value;
foreach (var o in _observers) o.Update(Symbol, old, value); // bildir
}
}
}Adım 2 Somut observer'lar
Her biri aynı bildirime farklı şekilde tepki veriyor.
public class PriceDisplay : IStockObserver
{
public void Update(string symbol, decimal oldPrice, decimal newPrice) =>
Console.WriteLine($" [Ekran] {symbol}: {newPrice:0.00} TL");
}
public class PriceAlert : IStockObserver
{
private readonly decimal _threshold;
public PriceAlert(decimal thresholdPercent) => _threshold = thresholdPercent;
public void Update(string symbol, decimal oldPrice, decimal newPrice)
{
var change = (newPrice - oldPrice) / oldPrice * 100;
if (Math.Abs(change) >= _threshold)
Console.WriteLine($" [UYARI] {symbol} %{change:0.0} değişti!");
}
}Adım 3 C# usulü: event ile aynı desen
event anahtar kelimesi, abone listesini ve ekleme/çıkarma işlemlerini bizim yerimize yönetir.
public record PriceChangedEventArgs(string Symbol, decimal NewPrice);
public class StockTicker
{
public event EventHandler<PriceChangedEventArgs>? PriceChanged;
public void Publish(string symbol, decimal price) =>
PriceChanged?.Invoke(this, new PriceChangedEventArgs(symbol, price));
}Adım 4 Kullanım
Tüm parçaları bir araya getiren istemci (client) kodu:
var thy = new Stock("THYAO", 300m);
var display = new PriceDisplay();
thy.Subscribe(display);
thy.Subscribe(new PriceAlert(thresholdPercent: 5));
Console.WriteLine("Fiyat 306 oldu:");
thy.Price = 306m;
Console.WriteLine("Fiyat 330 oldu:");
thy.Price = 330m;
thy.Unsubscribe(display);
Console.WriteLine("Ekran abonelikten çıktı. Fiyat 280 oldu:");
thy.Price = 280m;
Console.WriteLine("\n--- C# event ile ---");
var ticker = new StockTicker();
EventHandler<PriceChangedEventArgs> logger =
(sender, e) => Console.WriteLine($" [Log] {e.Symbol} = {e.NewPrice}");
ticker.PriceChanged += logger; // abone ol
ticker.Publish("ASELS", 61.75m);
ticker.PriceChanged -= logger; // abonelikten çık
ticker.Publish("ASELS", 62.10m); // kimse dinlemiyorKonsol çıktısı
Fiyat 306 oldu: [Ekran] THYAO: 306,00 TL Fiyat 330 oldu: [Ekran] THYAO: 330,00 TL [UYARI] THYAO %7,8 değişti! Ekran abonelikten çıktı. Fiyat 280 oldu: [UYARI] THYAO %-15,2 değişti! --- C# event ile --- [Log] ASELS = 61,75
Kodda dikkat edilmesi gerekenler
Stocksınıfı ekranı veya uyarı servisini tanımıyor; sadeceIStockObserverlistesini dolaşıyor.- Abonelikten çıkan ekran sonraki değişiklikten haberdar olmadı.
- Event örneğinde son
Publishçağrısı hiçbir çıktı üretmedi; çünkü abone kalmadı.?.Invokesayesinde abone yokken hata da oluşmadı.
event mekanizması bu desenin ta kendisidir.