Observer Tasarım Deseni Nedir?

Sayfayı kopyala
💡 Özet (TL;DR):
- Observer Nedir?: Bir nesnede (yayıncı/subject) meydana gelen olayları, onu takip eden diğer nesnelere (aboneler/observers) otomatik olarak bildiren davranışsal bir tasarım desenidir.
- Kullanım Amacı: Nesneler arasında gevşek bağlılık (loose coupling) kurarak, olay bazlı ve dinamik bir abonelik sistemi oluşturmak.
- Gerçek Dünya Benzetmesi: Abone olduğunuz bir e-bülten veya dergi; yeni sayı çıktığında kapınıza gelir, ilgilenmeyenlere ise spam yapılmaz.
Bu yazı Design Patterns / Tasarım Desenleri Nedir? başlıklı yazı dizisinin bir parçasıdır.
Bu içerik ağırlıklı olarak refactoring.guru sitesindeki içeriğin tercümesi ve derlenmesinden oluşturulmuştur. Tüm tasarım desenlerine yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.
Observer Deseninin Avantajları ve Dezavantajları
| Avantajları | Dezavantajları |
|---|---|
| Açık/Kapalı Prensibi (OCP): Yayıncı kodunu değiştirmeden yeni abone sınıfları ekleyebilirsiniz. | Abonelerin bilgilendirilme sırası üzerinde kontrolünüz yoktur (rastgele tetiklenirler). |
| Dinamik Abonelik: Çalışma zamanında (runtime) aboneler eklenebilir veya çıkarılabilir. | Aşırı kullanımda karmaşık bağımlılık zincirleri ve bellek sızıntıları (leak) oluşturabilir. |
| Gevşek Bağlılık (Loose Coupling): Yayıncı, abonelerin sınıflarını değil sadece ortak arayüzünü bilir. | Aboneler güncellemeleri pasif olarak dinler; gereksiz güncellemeler performans kaybına yol açabilir. |
Observer Tasarım Deseninin Amacı
Observer tasarım deseni, birden fazla nesneyi, takip ettikleri başka bir nesnede gerçekleşen olaylarla ilgili bilgilendirmeyi sağlayan bir abonelik mekanizması oluşturmayı amaçlar.
Sorun
Müşteri (Customer) ve Mağaza (Store) adında iki nesne tipiniz olduğunu düşünün. Müşteri, belirli bir markanın yakın zamanda piyasaya sürülecek ürünüyle son derece ilgili olsun. Müşteri mağazayı her gün ziyaret ediyor ve ürünün durumunu kontrol ediyor. Ancak ürün henüz gelmemişse yapılan tüm bu ziyaretler boşuna zaman ve kaynak kaybına yol açar.

Diğer bir seçenek olarak mağaza, her yeni ürün geldiğinde bütün müşterilerine (spam olarak kabul edilebilecek) on binlerce e-posta gönderebilir. Bu müşterilerin her gün mağazaya gelmesini engellese de bu ürünlerle ilgilenmeyen müşteriler için son derece sinir bozucu olacaktır.
Bu durumda ya müşteri her gün mağazaya gelip zaman ve kaynak harcayacaktır ya da mağaza tüm müşterilere gereksiz e-postalar gönderip zaman ve kaynak tüketecektir.
Çözüm
Bir nesnenin durumuyla ilgileniyorsak bu nesne bizim için ana bir öznedir (subject). Bu özneyi, durum değişikliğini başka nesnelere de haber vereceği için yayıncı (publisher) olarak adlandırmak daha doğru olur. Bu nesnenin durumunu takip etmek isteyen diğer nesneler de bu nesnenin aboneleridir (subscribers).
Observer deseni, yayıncının sınıfı içerisine bir abonelik mekanizması eklenmesini tavsiye eder. Böylece diğer nesneler bu nesnede gerçekleşen olaylara abone olabilir ya da abonelikten çıkabilir. Bu süreç temelde oldukça basittir. Süreç şöyle ilerler:
- Abone nesnelerin referanslarını saklayan bir dizi (array) alanı oluşturulur.
- Bu listeye abone eklemeyi veya çıkartmayı sağlayan birkaç dışa açık (public) metot tanımlanır.

Bu mekanizma ile birlikte yayıncı nesnede önemli bir olay gerçekleştiğinde, yayıncı tüm abonelerin üzerinden geçerek bu nesnelere özel bildirim metotlarını çağırır.
Gerçek dünyada uygulamaların tek bir yayıncı sınıfında gerçekleşen olaylarla ilgilenen onlarca abone sınıfı olabilir. Bütün bunları yayıncının içinde doğrudan belirtip sıkı bir bağımlılık (tight coupling) oluşturmak istemezsiniz; hatta çoğu zaman bu aboneleri önceden bilemeyebilirsiniz.
Bu nedenle tüm abonelerin, yayıncının kendileriyle iletişime geçebileceği ortak bir arayüzü paylaşmaları önemlidir. Bu arayüz, yayıncının bağlamla ilgili bilgileri aktarabileceği parametreleri de olan bir bildirim metodu tanımlamalıdır.

Uygulanabilirlik
Bir nesnedeki değişikliğin başka nesneleri de değiştirmesi gereken ve bu nesnelerin önceden bilinmesi mümkün olmayan durumlarda Observer desenini kullanabilirsiniz.
Bu sorunla genellikle kullanıcı arayüzleri (UI) ile çalışırken karşılaşırsınız. Örneğin bir buton sınıfı oluşturursunuz ve bu butona tıklandığında istemcinin belirleyeceği özel kodları tetikleyebilmesini istersiniz. Düğmelerinize bir abonelik mekanizması ekleyerek istemcinin düğmeye istediği özel kod parçalarını abone edebilmesini sağlarsınız.
Uygulamanızdaki bazı nesnelerin başka bir nesneyi, belirli durumlar veya belirli bir süre için izlemesi gereken durumlarda bu deseni kullanabilirsiniz.
Abonelik listesi dinamiktir; böylece aboneler istedikleri zaman sisteme dahil olabilir veya abonelikten çıkabilirler.
Diğer Tasarım Desenleri ile İlişkisi
- Chain of Responsibility, Command, Mediator ve Observer alıcı ve göndericileri birbirine bağlamak için çeşitli yöntemler önerir:
- Chain of Responsibility bir isteği potansiyel alıcılardan en az biri işleyene kadar dinamik bir alıcı zinciri boyunca sırayla iletir.
- Command göndericiler ve alıcılar arasında tek yönlü bağlantılar kurar.
- Mediator göndericiler ve alıcılar arasındaki doğrudan bağlantıları ortadan kaldırarak onları bir aracı nesne aracılığıyla dolaylı olarak iletişim kurmaya zorlar.
- Observer alıcıların isteklere dinamik olarak abone olmalarını ve abonelikten çıkmalarını sağlar.
- Mediator ve Observer arasındaki fark genellikle belirgin değildir. Çoğu durumda bu kalıplardan herhangi birini uygulayabilirsiniz fakat bazen ikisini aynı anda uygulamanız gerekebilir.
Mediator'ün birincil amacı, bir dizi sistem bileşeni arasındaki karşılıklı bağımlılıkları ortadan kaldırmaktır. Bileşenler bunun yerine tek bir aracı nesneye bağımlı hale gelir. Observer'ın amacı ise nesneler arasında bazılarının diğerlerinin alt nesnesi olarak hareket ettiği, dinamik tek taraflı bir bağlantı oluşturmaktır.
Mediator deseninin Observer tabanlı popüler bir uygulama yöntemi vardır. Bu yöntemde mediator yayıncı, bileşenler ise mediator'ün olaylarına (event) abone olan (subscribe) ya da abonelikten çıkan (unsubscribe) aboneler olarak hareket ederler. Mediator bu şekilde inşa edildiğinde Observer'a çok benzer.
Mediator ve Observer'ın ortak noktaları vardır ve bazen birbirleriyle iç içe geçebilirler, ama birbirlerinden tamamen farklı kullanımları da vardır. Örneğin tüm bileşenleri kalıcı olarak bir mediator nesnesine bağlayabilirsiniz. Bu uygulama Observer'a benzemez ve hâlâ bir Mediator desenidir.
Observer Deseni Kod Örnekleri
Örnek PHP Kodu
<?php
namespace RefactoringGuru\Observer\Conceptual;
/**
* PHP, Observer deseniyle ilgili yerleşik arayüzlere sahiptir.
*
* SplSubject arayüzü şu şekildedir:
* @link http://php.net/manual/en/class.splsubject.php
*/
class Subject implements \SplSubject
{
/**
* @var int Aboneler için önemli olan özne durumu.
*/
public $state;
/**
* @var \SplObjectStorage Abonelerin listesi.
*/
private $observers;
public function __construct()
{
$this->observers = new \SplObjectStorage();
}
/**
* Abonelik yönetim metotları.
*/
public function attach(\SplObserver $observer): void
{
echo "Subject: Attached an observer.\n";
$this->observers->attach($observer);
}
public function detach(\SplObserver $observer): void
{
$this->observers->detach($observer);
echo "Subject: Detached an observer.\n";
}
/**
* Tüm aboneleri güncellemeler hakkında bilgilendirir.
*/
public function notify(): void
{
echo "Subject: Notifying observers...\n";
foreach ($this->observers as $observer) {
$observer->update($this);
}
}
/**
* Özne, kendi iş mantığına göre durum değişikliği yaptığında aboneleri uyarır.
*/
public function someBusinessLogic(): void
{
echo "\nSubject: I'm doing something important.\n";
$this->state = rand(0, 10);
echo "Subject: My state has just changed to: {$this->state}\n";
$this->notify();
}
}
/**
* Somut Aboneler, bağlı oldukları Özne'den gelen güncellemelere tepki verir.
*/
class ConcreteObserverA implements \SplObserver
{
public function update(\SplSubject $subject): void
{
if ($subject->state < 3) {
echo "ConcreteObserverA: Reacted to the event.\n";
}
}
}
class ConcreteObserverB implements \SplObserver
{
public function update(\SplSubject $subject): void
{
if ($subject->state == 0 || $subject->state >= 2) {
echo "ConcreteObserverB: Reacted to the event.\n";
}
}
}
/**
* İstemci Kodu.
*/
$subject = new Subject();
$o1 = new ConcreteObserverA();
$subject->attach($o1);
$o2 = new ConcreteObserverB();
$subject->attach($o2);
$subject->someBusinessLogic();
$subject->someBusinessLogic();
$subject->detach($o2);
$subject->someBusinessLogic();
Örnek Python Kodu
from __future__ import annotations
from abc import ABC, abstractmethod
from random import randrange
from typing import List
class Subject(ABC):
"""
Subject arayüzü, aboneleri yönetmek için gereken metotları tanımlar.
"""
@abstractmethod
def attach(self, observer: Observer) -> None:
pass
@abstractmethod
def detach(self, observer: Observer) -> None:
pass
@abstractmethod
def notify(self) -> None:
pass
class ConcreteSubject(Subject):
"""
Özne önemli bir duruma sahiptir ve bu durum değiştiğinde aboneleri bilgilendirir.
"""
_state: int = None
_observers: List[Observer] = []
def attach(self, observer: Observer) -> None:
print("Subject: Attached an observer.")
self._observers.append(observer)
def detach(self, observer: Observer) -> None:
self._observers.remove(observer)
def notify(self) -> None:
"""
Tüm aboneleri güncellemeler hakkında tetikler.
"""
print("Subject: Notifying observers...")
for observer in self._observers:
observer.update(self)
def some_business_logic(self) -> None:
print("\nSubject: I'm doing something important.")
self._state = randrange(0, 10)
print(f"Subject: My state has just changed to: {self._state}")
self.notify()
class Observer(ABC):
"""
Observer arayüzü, özneler tarafından çağrılan güncelleme metodunu tanımlar.
"""
@abstractmethod
def update(self, subject: Subject) -> None:
pass
"""
Somut Aboneler, bağlı oldukları Özne'den gelen güncellemelere tepki verir.
"""
class ConcreteObserverA(Observer):
def update(self, subject: Subject) -> None:
if subject._state < 3:
print("ConcreteObserverA: Reacted to the event")
class ConcreteObserverB(Observer):
def update(self, subject: Subject) -> None:
if subject._state == 0 or subject._state >= 2:
print("ConcreteObserverB: Reacted to the event")
if __name__ == "__main__":
# İstemci Kodu.
subject = ConcreteSubject()
observer_a = ConcreteObserverA()
subject.attach(observer_a)
observer_b = ConcreteObserverB()
subject.attach(observer_b)
subject.some_business_logic()
subject.some_business_logic()
subject.detach(observer_a)
subject.some_business_logic()
Diğer Tasarım Kalıpları / Design Patterns
Oluşumsal Kalıplar (Creational Patterns)
Factory Method, Abstract Factory, Builder, Prototype, Singleton
Yapısal Kalıplar (Structural Patterns)
Adapter, Bridge, Composite, Decorator, Facade, Flyweight, Proxy
Davranışsal Kalıplar (Behavioral Patterns)
Chain of Responsibility, Command, Iterator, Mediator, Memento, Observer, State, Strategy, Template Method, Visitor
