Observer Design Pattern: Nesneler Arası Olay Tabanlı İletişim
Observer Design Pattern: Nesneler Arası Olay Tabanlı İletişim
YouTube'da bir kanala abone olduğunuzda ne oluyor? Kanal yeni video yüklediğinde bildirim alıyorsunuz. Aboneliği iptal ettiğinizde bildirimler duruyor. Kanalın sizi tek tek aramasına gerek yok — sisteme bir kez "beni haberdar et" diyorsunuz, gerisini altyapı hallediyor. İşte Observer Design Pattern tam olarak bu mekanizmayı yazılıma taşıyor: bir nesne durumu değiştiğinde, ona bağımlı tüm nesneler otomatik olarak haberdar ediliyor.
Bu pattern, yazılım dünyasının en yaygın kullanılan tasarım kalıplarından biri. GUI framework'lerindeki buton tıklama olaylarından, Spring Framework'ün event sistemine, React'in state yönetiminden message broker'lara kadar her yerde karşınıza çıkıyor. Hatta farkında olmadan her gün kullanıyorsunuz — bir event listener yazdığınızda, bir webhook kurduğunuzda veya bir Kafka consumer oluşturduğunuzda arka planda çalışan mantık Observer Pattern'dır.
Bu yazıda Observer Pattern'ı sıfırdan inşa edeceksiniz. Hangi problemi çözdüğünü, nasıl implemente edildiğini, gerçek projelerde nasıl kullanıldığını ve hangi tuzaklardan kaçınmanız gerektiğini öğreneceksiniz. Yazıyı bitirdiğinizde bu pattern'ı ne zaman kullanacağınızı, ne zaman kullanmamanız gerektiğini ve profesyonel alternatifleri kesin olarak bileceksiniz.
Problem: Sıkı Bağımlılık ve Sürekli Kontrol
Observer Pattern'ın çözdüğü sorunu anlamak için bir senaryo düşünelim. Bir e-ticaret platformu geliştiriyorsunuz ve yeni bir sipariş oluştuğunda şu işlemlerin yapılması gerekiyor:
Müşteriye e-posta gönderilmesi
Stok miktarının güncellenmesi
Muhasebe sistemine kayıt eklenmesi
Kargo firmasına bildirim yapılması
Bunu pattern bilmeden yazarsanız, muhtemelen şöyle bir kod çıkar:
class OrderService {
private EmailService emailService;
private StockService stockService;
private AccountingService accountingService;
private ShippingService shippingService;
public void createOrder(Order order) {
// Siparişi kaydet
orderRepository.save(order);
// Herkese tek tek haber ver
emailService.sendOrderConfirmation(order);
stockService.reduceStock(order);
accountingService.recordSale(order);
shippingService.schedulePickup(order);
}
}Bu kod çalışır, ama ciddi problemleri var. Birincisi, OrderService dört farklı servise doğrudan bağımlı. Bu servislerin herhangi birinde değişiklik olursa, OrderService'i de değiştirmek zorundasınız. İkincisi, yeni bir gereksinim geldiğinde — mesela sipariş sonrası SMS gönderilmesi — yine OrderService'i açıp değiştirmeniz gerekiyor. Her yeni özellik eklediğinizde bu sınıfı modifiye ediyorsunuz ve bu Open/Closed Principle'ı (açık-kapalı ilkesi) ihlal ediyor.
Üçüncü ve en sinsi problem şu: bu servislerden biri hata fırlatırsa ne olacak? emailService patlarsa stok güncellenmeyecek mi? Her birini try-catch ile sarmalamanız gerekiyor ve bu da kodu bir spagettiye çeviriyor.
Observer Pattern, bu problemlerin hepsini tek bir zarif çözümle ortadan kaldırıyor.
Observer Pattern'ın Anatomisi
Observer Pattern iki temel rol üzerine kurulu:
Subject (Yayıncı): Durumu değişen ve bu değişikliği duyurmak isteyen nesne. YouTube analojisinde kanal sahibi. Observer'ları kaydetmek, silmek ve bildirim göndermek için metotlar sunar.
Observer (Gözlemci/Abone): Subject'teki değişikliklerden haberdar olmak isteyen nesne. YouTube analojisinde abone. Subject'ten bildirim aldığında ne yapacağını tanımlayan bir metoda sahiptir.
🎯 Analoji: Observer Pattern'ı bir radyo yayını gibi düşünün. Radyo istasyonu (Subject) sürekli yayın yapar. Dinleyiciler (Observer) frekansı ayarladığında yayını alır, kapatınca almaz. İstasyon her dinleyiciye ayrı ayrı bağlanmaz — yayınını yapar, alan alır.
Bu yapıda Subject, Observer'ların kim olduğunu veya ne yaptığını bilmek zorunda değil. Tek bildiği şey: "bir liste var, listedekilere haber ver." Bu gevşek bağlılık (loose coupling) Observer Pattern'ın en güçlü özelliğidir.
Java ile Sıfırdan Observer Pattern İmplementasyonu
Şimdi bu pattern'ı sıfırdan inşa edelim. Önce Observer ve Subject arayüzlerini tanımlayalım:
// Gözlemci arayüzü — haber almak isteyen herkes bunu implemente eder
interface EventObserver {
void onEvent(String eventType, Object data);
}
// Yayıncı — olayları yöneten merkezi sınıf
class EventManager {
// Her olay tipi için ayrı bir observer listesi
private Map<String, List<EventObserver>> listeners = new HashMap<>();
public void subscribe(String eventType, EventObserver observer) {
listeners.computeIfAbsent(eventType, k -> new ArrayList<>()).add(observer);
}
public void unsubscribe(String eventType, EventObserver observer) {
List<EventObserver> observers = listeners.get(eventType);
if (observers != null) {
observers.remove(observer);
}
}
public void notify(String eventType, Object data) {
List<EventObserver> observers = listeners.get(eventType);
if (observers != null) {
for (EventObserver observer : observers) {
observer.onEvent(eventType, data);
}
}
}
}Burada EventManager sınıfı Subject rolünü üstleniyor. Dikkat edin: observer'ları basit bir liste yerine Map<String, List<EventObserver>> ile tutuyoruz. Bu, farklı olay tiplerini desteklememizi sağlıyor — bir observer sadece ilgilendiği olaylara abone olabiliyor.
computeIfAbsent metodunu ilk kez görüyorsanız şöyle açıklayalım: verilen anahtar (key) Map'te yoksa, sağlanan fonksiyonla yeni bir değer oluşturup ekliyor. Bu sayede her subscribe çağrısında "liste var mı yok mu" kontrolü yapmamıza gerek kalmıyor.
Şimdi bu altyapıyı e-ticaret senaryomuzda kullanalım:
// Sipariş sınıfı
class Order {
private String id;
private String customerEmail;
private List<String> items;
private double totalAmount;
public Order(String id, String customerEmail, List<String> items, double totalAmount) {
this.id = id;
this.customerEmail = customerEmail;
this.items = items;
this.totalAmount = totalAmount;
}
// Getter'lar
public String getId() { return id; }
public String getCustomerEmail() { return customerEmail; }
public List<String> getItems() { return items; }
public double getTotalAmount() { return totalAmount; }
}
// E-posta observer'ı
class EmailNotificationObserver implements EventObserver {
@Override
public void onEvent(String eventType, Object data) {
Order order = (Order) data;
System.out.println("[EMAIL] Sipariş onay e-postası gönderildi: "
+ order.getCustomerEmail()
+ " — Tutar: " + order.getTotalAmount() + " TL");
}
}
// Stok observer'ı
class StockUpdateObserver implements EventObserver {
@Override
public void onEvent(String eventType, Object data) {
Order order = (Order) data;
System.out.println("[STOK] Stok güncellendi. Ürünler: "
+ String.join(", ", order.getItems()));
}
}
// Muhasebe observer'ı
class AccountingObserver implements EventObserver {
@Override
public void onEvent(String eventType, Object data) {
Order order = (Order) data;
System.out.println("[MUHASEBE] Satış kaydı oluşturuldu. Sipariş: "
+ order.getId() + " — " + order.getTotalAmount() + " TL");
}
}
// Sipariş servisi — artık hiçbir servise doğrudan bağımlı değil
class OrderService {
private EventManager eventManager;
public OrderService(EventManager eventManager) {
this.eventManager = eventManager;
}
public void createOrder(Order order) {
System.out.println("Sipariş oluşturuldu: " + order.getId());
// Siparişi kaydet (veritabanı işlemi)
// orderRepository.save(order);
// Tek satırla herkesi haberdar et
eventManager.notify("order:created", order);
}
public void cancelOrder(Order order) {
System.out.println("Sipariş iptal edildi: " + order.getId());
eventManager.notify("order:cancelled", order);
}
}
// Kullanım
class Main {
public static void main(String[] args) {
// Event manager oluştur
EventManager eventManager = new EventManager();
// Observer'ları kaydet
eventManager.subscribe("order:created", new EmailNotificationObserver());
eventManager.subscribe("order:created", new StockUpdateObserver());
eventManager.subscribe("order:created", new AccountingObserver());
// Sipariş servisi
OrderService orderService = new OrderService(eventManager);
// Sipariş oluştur
Order order = new Order(
"ORD-001",
"ahmet@example.com",
List.of("Laptop", "Mouse"),
25999.99
);
orderService.createOrder(order);
}
}
// Çıktı:
// Sipariş oluşturuldu: ORD-001
// [EMAIL] Sipariş onay e-postası gönderildi: ahmet@example.com — Tutar: 25999.99 TL
// [STOK] Stok güncellendi. Ürünler: Laptop, Mouse
// [MUHASEBE] Satış kaydı oluşturuldu. Sipariş: ORD-001 — 25999.99 TLGördüğünüz gibi OrderService artık ne EmailService'i biliyor, ne StockService'i, ne de AccountingService'i. Sadece EventManager'a "sipariş oluştu" diyor, gerisini observer'lar hallediyor.
Yeni bir gereksinim geldi: sipariş oluştuğunda SMS de gönderilsin. Ne yapıyoruz? OrderService'e dokunmuyoruz bile:
// Yeni observer — mevcut koda hiç dokunmadan ekleniyor
class SmsNotificationObserver implements EventObserver {
@Override
public void onEvent(String eventType, Object data) {
Order order = (Order) data;
System.out.println("[SMS] Sipariş bildirimi gönderildi: " + order.getId());
}
}
// Sadece kayıt et, tamam
eventManager.subscribe("order:created", new SmsNotificationObserver());Tek bir satırla yeni özellik eklendi. OrderService değişmedi, mevcut observer'lar etkilenmedi. Bu, Open/Closed Principle'ın mükemmel bir uygulaması: sınıf değişikliğe kapalı, genişlemeye açık.
Gerçek Dünya: Spring Framework Event Sistemi
Spring Framework'ün kendi event (olay) sistemi, Observer Pattern'ın profesyonel bir implementasyonudur. Kendi EventManager sınıfınızı yazmak yerine Spring'in hazır altyapısını kullanabilirsiniz. Hem daha güvenli, hem daha güçlü.
// Spring Event sınıfı — bildirimle taşınacak veri
public class OrderCreatedEvent {
private final String orderId;
private final String customerEmail;
private final double totalAmount;
public OrderCreatedEvent(String orderId, String customerEmail, double totalAmount) {
this.orderId = orderId;
this.customerEmail = customerEmail;
this.totalAmount = totalAmount;
}
public String getOrderId() { return orderId; }
public String getCustomerEmail() { return customerEmail; }
public double getTotalAmount() { return totalAmount; }
}
// Event yayınlayan servis (Subject rolü)
@Service
public class OrderService {
private final ApplicationEventPublisher eventPublisher;
public OrderService(ApplicationEventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
}
public void createOrder(String orderId, String email, double amount) {
// Sipariş veritabanına kaydedilir...
System.out.println("Sipariş kaydedildi: " + orderId);
// Event yayınla — kim dinliyorsa o bilir
eventPublisher.publishEvent(new OrderCreatedEvent(orderId, email, amount));
}
}
// E-posta dinleyicisi (Observer rolü)
@Component
public class EmailEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("[EMAIL] Onay e-postası gönderildi: " + event.getCustomerEmail());
}
}
// Stok dinleyicisi
@Component
public class StockEventListener {
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("[STOK] Stok güncellendi: sipariş " + event.getOrderId());
}
}
// Asenkron dinleyici — ana thread'i bloklamadan çalışır
@Component
public class AnalyticsEventListener {
@Async
@EventListener
public void handleOrderCreated(OrderCreatedEvent event) {
System.out.println("[ANALİTİK] Sipariş analitik kaydı: " + event.getTotalAmount() + " TL");
}
}Spring'in event sistemi birkaç önemli avantaj sunuyor. Birincisi, subscribe / unsubscribe işlemleriyle uğraşmıyorsunuz — @EventListener anotasyonu koymanız yeterli, Spring gerisini hallediyor. İkincisi, @Async anotasyonu ile event listener'ı ayrı bir thread'de çalıştırabiliyorsunuz. Bu, e-posta gönderme gibi yavaş işlemlerin sipariş oluşturma süresini etkilememesini sağlıyor.
Üçüncüsü ve belki en önemlisi: Spring event sistemi transactional olaylarda da çalışıyor. @TransactionalEventListener ile bir event'in sadece transaction başarıyla commit edildikten sonra işlenmesini sağlayabilirsiniz. Bu, veritabanı işlemi başarısız olursa e-postanın gönderilmemesi gibi tutarlılık garantisi verir.
// Sadece transaction başarılı olursa çalışır
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleOrderCreated(OrderCreatedEvent event) {
// Transaction rollback olursa bu metot ÇALIŞMAZ
emailService.sendConfirmation(event.getCustomerEmail());
}Python ile Observer Pattern
Observer Pattern dile bağımlı değil. Python'da aynı pattern'ı daha kısa ve okunabilir şekilde yazabilirsiniz. Python'un first-class function (birinci sınıf fonksiyon) desteği sayesinde observer olarak sınıf yerine fonksiyon da kullanabilirsiniz:
class EventEmitter:
"""Olay yönetim sistemi — Observer Pattern'ın Python implementasyonu"""
def __init__(self):
self._listeners = {} # {"event_name": [callback1, callback2, ...]}
def on(self, event_type, callback):
"""Bir olaya abone ol"""
if event_type not in self._listeners:
self._listeners[event_type] = []
self._listeners[event_type].append(callback)
return self # Method chaining için
def off(self, event_type, callback):
"""Aboneliği iptal et"""
if event_type in self._listeners:
self._listeners[event_type].remove(callback)
def emit(self, event_type, *args, **kwargs):
"""Olayı tetikle — tüm dinleyicilere haber ver"""
for callback in self._listeners.get(event_type, []):
callback(*args, **kwargs)
# Kullanım — fonksiyon tabanlı observer'lar
class PaymentProcessor:
def __init__(self):
self.events = EventEmitter()
def process_payment(self, user, amount):
print(f"Ödeme işleniyor: {user} — {amount} TL")
# Ödeme başarılı
self.events.emit("payment:success", user=user, amount=amount)
def refund(self, user, amount):
print(f"İade işleniyor: {user} — {amount} TL")
self.events.emit("payment:refund", user=user, amount=amount)
# Observer'lar basit fonksiyonlar olabilir
def send_receipt(user, amount):
print(f"[FATURA] {user} adına {amount} TL'lik makbuz gönderildi")
def update_loyalty_points(user, amount):
points = int(amount / 10)
print(f"[SADAKAT] {user} hesabına {points} puan eklendi")
def log_transaction(user, amount):
print(f"[LOG] İşlem kaydı: {user} — {amount} TL")
def notify_refund(user, amount):
print(f"[BİLDİRİM] {user} adına {amount} TL iade bildirimi gönderildi")
# Kurulum
processor = PaymentProcessor()
# Observer'ları kaydet — method chaining ile
processor.events \
.on("payment:success", send_receipt) \
.on("payment:success", update_loyalty_points) \
.on("payment:success", log_transaction) \
.on("payment:refund", notify_refund) \
.on("payment:refund", log_transaction)
# Test et
processor.process_payment("Ayşe", 500)
print("---")
processor.refund("Ayşe", 500)
# Çıktı:
# Ödeme işleniyor: Ayşe — 500 TL
# [FATURA] Ayşe adına 500 TL'lik makbuz gönderildi
# [SADAKAT] Ayşe hesabına 50 puan eklendi
# [LOG] İşlem kaydı: Ayşe — 500 TL
# ---
# İade işleniyor: Ayşe — 500 TL
# [BİLDİRİM] Ayşe adına 500 TL iade bildirimi gönderildi
# [LOG] İşlem kaydı: Ayşe — 500 TLPython versiyonunun ne kadar kompakt olduğuna dikkat edin. Java'da arayüz tanımlamanız, sınıf oluşturmanız ve metot override etmeniz gereken şeyi, Python'da sadece bir fonksiyon yazarak çözüyorsunuz. Bu, Python'un first-class function desteğinin gücü — fonksiyonlar nesne gibi davranır, bir listeye eklenebilir, parametre olarak geçilebilir.
method chaining tekniğine de dikkat edin: on() metodu self döndürdüğü için .on().on().on() şeklinde zincirleme çağrı yapabiliyoruz. Bu, kodu hem kısa hem de okunabilir kılıyor.
Yaygın Hatalar ve Tuzaklar
Observer Pattern doğru kullanıldığında harika, ama birkaç kritik tuzağı var. Bunları bilmezseniz debug etmesi çok zor hatalarla karşılaşırsınız.
1. Memory Leak: Aboneliği İptal Etmemek
Bu en yaygın ve en tehlikeli hata. Bir observer kaydedilir ama işi bittiğinde listeden çıkarılmazsa, Subject o observer'a güçlü bir referans (strong reference) tutar. Garbage collector (çöp toplayıcı) bu nesneyi temizleyemez ve bellek sızıntısı (memory leak) oluşur.
// ❌ YANLIŞ — observer asla temizlenmiyor
class Dashboard {
public void init(EventManager eventManager) {
// Bu observer sonsuza kadar bellekte kalır
eventManager.subscribe("data:updated", (type, data) -> {
updateChart(data);
});
}
}
// ✅ DOĞRU — observer referansını sakla, gerektiğinde temizle
class Dashboard {
private EventObserver chartUpdater;
public void init(EventManager eventManager) {
this.chartUpdater = (type, data) -> updateChart(data);
eventManager.subscribe("data:updated", chartUpdater);
}
public void destroy(EventManager eventManager) {
// Sayfa kapatıldığında veya dashboard kaldırıldığında
eventManager.unsubscribe("data:updated", chartUpdater);
}
}Bu sorun özellikle GUI uygulamalarında ve uzun süre çalışan sunucu uygulamalarında kritik. Her pencere açılışında observer ekleniyor, pencere kapanınca temizlenmiyorsa saatler sonra uygulama bellek yetersizliğinden çöker.
2. Sıralama Bağımlılığı
Observer'lar arasında sıralama garantisi yoktur. Bir observer'ın diğerinden önce çalışmasına bağımlı kod yazmak tehlikelidir:
// ❌ YANLIŞ — StockObserver'ın EmailObserver'dan önce çalışmasına bağımlı
class EmailObserver implements EventObserver {
@Override
public void onEvent(String eventType, Object data) {
Order order = (Order) data;
// "Stok güncellenmiştir" bilgisini e-postaya yazıyor
// AMA StockObserver henüz çalışmamış olabilir!
String stockStatus = stockService.getStatus(order); // Tutarsız olabilir
sendEmail(order, stockStatus);
}
}Observer'lar birbirinden bağımsız olmalıdır. Eğer sıralama gerekiyorsa, bu observer'ları tek bir observer'da birleştirin veya farklı bir olay zinciri (event chain) tasarlayın.
3. Observer İçinde Exception Yönetimi
Bir observer'da fırlatılan exception, sonraki observer'ların çalışmasını engelleyebilir. Bunu mutlaka ele almalısınız:
// ❌ YANLIŞ — bir observer patlarsa gerisine haber gitmez
public void notify(String eventType, Object data) {
for (EventObserver observer : listeners.get(eventType)) {
observer.onEvent(eventType, data); // Burası patlarsa döngü durur!
}
}
// ✅ DOĞRU — her observer bağımsız çalışır
public void notify(String eventType, Object data) {
List<EventObserver> observers = listeners.getOrDefault(eventType, List.of());
for (EventObserver observer : observers) {
try {
observer.onEvent(eventType, data);
} catch (Exception e) {
System.err.println("Observer hatası [" + eventType + "]: " + e.getMessage());
// Hata loglanır ama diğer observer'lar çalışmaya devam eder
}
}
}Bu basit try-catch, prodüksiyon sistemlerinde hayat kurtarır. E-posta gönderimi başarısız oldu diye stok güncellemesinin de atlanması kabul edilemez.
4. ConcurrentModificationException Tuzağı
Observer listesi üzerinde iterasyon yaparken aynı anda listeye ekleme veya çıkarma yapılırsa ConcurrentModificationException (eşzamanlı değişiklik hatası) alırsınız. Bu özellikle çok thread'li (multithreaded) uygulamalarda başınızı ağrıtır:
// ❌ YANLIŞ — iterasyon sırasında listeye müdahale
public void notify(String eventType, Object data) {
for (EventObserver observer : listeners.get(eventType)) {
observer.onEvent(eventType, data);
// Eğer observer içinde unsubscribe çağrılırsa → BOOM
}
}
// ✅ DOĞRU — listenin kopyası üzerinde iterasyon yap
public void notify(String eventType, Object data) {
List<EventObserver> observers = listeners.get(eventType);
if (observers == null) return;
// Snapshot al — orijinal liste değişse bile bu kopya etkilenmez
List<EventObserver> snapshot = new ArrayList<>(observers);
for (EventObserver observer : snapshot) {
try {
observer.onEvent(eventType, data);
} catch (Exception e) {
System.err.println("Observer hatası: " + e.getMessage());
}
}
}new ArrayList<>(observers) ile listenin o anki halinin bir kopyasını alıyoruz. Artık iterasyon sırasında orijinal listeye ekleme veya çıkarma yapılsa bile kopyamız etkilenmiyor. Bu teknik "snapshot iteration" olarak bilinir.
Alternatif olarak CopyOnWriteArrayList kullanabilirsiniz — bu koleksiyon her yazma işleminde otomatik olarak yeni bir kopya oluşturur. Okuma ağırlıklı senaryolarda harika çalışır, ama sık yazma olan durumlarda performans kaybına yol açar.
Best Practices: Profesyonel Observer Kullanımı
1. Olay İsimlendirmesinde Tutarlı Olun
Olay isimlerini rastgele koymak yerine tutarlı bir konvansiyon (convention) belirleyin:
// ✅ İyi — "kaynak:aksiyon" formatı, geçmiş zaman
"order:created"
"user:registered"
"payment:completed"
"stock:updated"
// ❌ Kötü — tutarsız, belirsiz
"newOrder"
"handleUser"
"PAYMENT"
"update_stock"kaynak:aksiyon formatında geçmiş zaman kullanmak (created, completed, updated) okunabilirliği artırır ve olayın ne anlama geldiğini tek bakışta anlamanızı sağlar. Bu yaklaşım, event-driven architecture'da (olay güdümlü mimari) yaygın bir standarttır.
2. Event Nesnelerini Immutable (Değiştirilemez) Yapın
Observer'lar arasında paylaşılan event nesnesi, bir observer tarafından değiştirilirse diğer observer'lar tutarsız veri görür. Event nesnelerini her zaman immutable yapın:
// ✅ Immutable event — Java record ile
public record OrderCreatedEvent(
String orderId,
String customerEmail,
double totalAmount,
Instant createdAt
) {
// Record'lar doğal olarak immutable — setter yok,
// alanlar final, constructor otomatik
}Java 16+ kullanıyorsanız record bu iş için biçilmiş kaftan. Getter'lar, equals, hashCode ve toString otomatik oluşturulur, alanlar final'dır, setter yoktur. Eğer eski Java sürümü kullanıyorsanız alanları final yapın ve setter metotları oluşturmayın.
3. Asenkron Observer'larda Dikkatli Olun
Observer'lar varsayılan olarak senkron çalışır — yani Subject'in notify metodu, tüm observer'lar tamamlanana kadar bloklanır. Eğer bir observer yavaş bir işlem yapıyorsa (e-posta gönderme, HTTP çağrısı, dosya yazma), Subject'in performansını düşürür.
Bu durumda observer'ları asenkron çalıştırmak mantıklıdır, ama dikkatli olmalısınız. Asenkron observer'larda hata yönetimi, sıralama ve transaction yönetimi karmaşıklaşır.
Spring'de @Async anotasyonu bunu kolaylaştırır. Kendi implementasyonunuzda ExecutorService kullanabilirsiniz:
class AsyncEventManager extends EventManager {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
@Override
public void notify(String eventType, Object data) {
List<EventObserver> observers = getObservers(eventType);
if (observers == null) return;
List<EventObserver> snapshot = new ArrayList<>(observers);
for (EventObserver observer : snapshot) {
executor.submit(() -> {
try {
observer.onEvent(eventType, data);
} catch (Exception e) {
System.err.println("Async observer hatası: " + e.getMessage());
}
});
}
}
// Uygulama kapatılırken thread pool'u temizle
public void shutdown() {
executor.shutdown();
}
}4. Observer Sayısını Kontrol Altında Tutun
Bir olaya yüzlerce observer bağlamak teknik olarak mümkün ama pratik olarak sorunludur. Bildirim süresi uzar, debug zorlaşır, yan etkiler takip edilemez hale gelir. Bir olaya 5-10'dan fazla observer bağladığınızı fark ediyorsanız, tasarımı gözden geçirmenin zamanı gelmiştir. Belki bazı observer'ları birleştirmelisiniz, belki de bir message broker (mesaj aracısı) kullanmaya geçmelisiniz.
Observer vs Pub/Sub vs Event Bus: Aradaki Fark
Bu üç kavram sıkça karıştırılır. Aralarındaki farkı netleştirelim:
Observer Pattern: Subject, observer'larını doğrudan tanır ve doğrudan çağırır. İlişki 1:N (bire çok). Aynı süreç (process) içinde çalışır. Bu yazıda anlattığımız budur.
Publish/Subscribe (Pub/Sub): Publisher ve subscriber birbirini tanımaz. Aralarında bir message broker (Kafka, RabbitMQ, Redis Pub/Sub) vardır. Farklı süreçler, farklı sunucular hatta farklı programlama dilleri arasında çalışabilir. Ölçeklenebilirlik ve dağıtık sistemler (distributed systems) için idealdir.
Event Bus: Observer Pattern'ın merkezi bir aracı (mediator) ile genişletilmiş hali. Tüm olaylar merkezi bir "bus" üzerinden yönlendirilir. Google'ın Guava kütüphanesindeki EventBus veya GreenRobot'un Android'deki EventBus'ı buna örnektir.
Basit bir kuralı hatırlayın: aynı uygulama içinde ve aynı JVM'de çalışıyorsanız Observer Pattern veya Spring Events yeterli. Farklı servisler veya microservice'ler arası iletişim gerekiyorsa Kafka veya RabbitMQ gibi bir message broker'a geçin.
Ne Zaman Observer Kullanmalı, Ne Zaman Kullanmamalı?
Kullanın:
Bir nesnenin durumu değiştiğinde birden fazla bağımsız bileşenin haberdar olması gerektiğinde
Bileşenler arasındaki bağımlılığı azaltmak istediğinizde
Çalışma zamanında (runtime) yeni dinleyiciler eklenmesi veya çıkarılması gerektiğinde
Event-driven mimari tasarlıyorsanız
Kullanmayın:
Observer sayısı her zaman bir ise — bu durumda basit bir metot çağrısı yeterli
Observer'lar arası sıralama kritik ise — Observer Pattern sıralama garantisi vermez
Performans kritik hot path'lerde (sıcak yollarda) — observer çağrıları ek maliyet getirir
Akış çok karmaşıklaşıyorsa ve debug edilemez hale geliyorsa — bu "event soup" olarak bilinir
⚠️ Dikkat: "Event soup" (olay çorbası) Observer Pattern'ın en büyük riskidir. Onlarca farklı event, düzinelerce observer, zincirleme event'ler (bir observer'ın başka bir event tetiklemesi) — bu noktada akışı takip etmek imkansız hale gelir. Event sayısı artınca bir diagram çizin ve akışı görselleştirin. Akış haritası kafanızda oluşmuyorsa, tasarımınız fazla karmaşıklaşmış demektir.
💡 İpucu: Observer Pattern'ı kullanmaya başlarken küçük düşünün. Bir servisinizde 3-4 observer ile başlayın, sistemin davranışını gözlemleyin, sonra yavaşça genişletin. "Her şeyi event'e çevirelim" yaklaşımı genellikle felaketle sonuçlanır.
Sonuç
Observer Design Pattern, nesneler arası iletişimi gevşek bağlı (loosely coupled) ve esnek bir şekilde kurmanızı sağlayan güçlü bir tasarım kalıbıdır. Bu yazıda öğrendiklerimizi özetleyelim:
Observer Pattern, bir nesnenin durumu değiştiğinde bağımlı nesneleri otomatik olarak haberdar eden bir davranışsal (behavioral) tasarım kalıbıdır
Pattern iki temel bileşenden oluşur: Subject (yayıncı) ve Observer (gözlemci/abone)
Java'da sıfırdan
EventManageryazarak veya Spring Events (@EventListener) kullanarak implemente edebilirsinizPython'da first-class function desteği sayesinde observer olarak doğrudan fonksiyon geçirebilirsiniz
En kritik tuzaklar: memory leak (abonelik temizlememek), exception yönetimi (bir observer'ın diğerlerini engellemesi) ve ConcurrentModificationException
Event nesnelerini immutable yapın, olay isimlerinde tutarlı konvansiyon kullanın, observer sayısını kontrol altında tutun
Aynı uygulama içinde Observer Pattern veya Spring Events yeterli; farklı servisler arası iletişimde message broker (Kafka, RabbitMQ) tercih edin
Event soup riskine dikkat edin — her şeyi event'e çevirmek çözüm değil, doğru yerde doğru miktarda kullanmak çözüm
Observer Pattern'ı bir kez içselleştirdiğinizde, kodunuzu organize etme şekliniz değişir. "Şu servisi çağırayım" yerine "bu olayı yayınlayayım, ilgilenen dinlesin" diye düşünmeye başlarsınız. Bu paradigma değişimi, daha esnek, daha test edilebilir ve daha bakımı kolay sistemler kurmanızın anahtarıdır.
Bu yazıyı beğendiniz mi?
Bültene abone olun ve yeni yazılardan ilk siz haberdar olun. Spam yok, söz.
İlgili Yazılar
Strategy Design Pattern: Koşullu Mantığı Zarif Kodla Değiştirmenin Yolu
Strategy Design Pattern nedir, neden kullanılır, Java ve Spring Boot ile nasıl uygulanır? if-else cehenneminden kurtulma...
Clean Code: Okunabilir Kod Yazmanın 10 Altın Kuralı
Robert C. Martin'in Clean Code prensiplerinden ilham alan 10 altın kural: anlamlı isimlendirme, küçük fonksiyonlar, DRY,...
Microservices İletişim Desenleri
Microservice mimarisinde servisler arası iletişim: REST, gRPC, Message Queue, Circuit Breaker, Saga Pattern ve Event-Dri...