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 desenleri ya da diğer adıyla tasarım kalıplarına yönelik ayrıntılı içeriklere yazının sonundaki bağlantılardan ulaşabilirsiniz.

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

Customer (Müşteri) ve Store (Dükkan) 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ü ile son derece ilgili olsun. Müşteri mağazayı her gün ziyaret ediyor ve ürünün durumunu kontrol ediyor. Ancak ürün hala yoldayken bu ziyaretler de boşu boşuna yapılmış oluyor.

Neden observer deseni
Mağazayı ziyaret etmek ya da gereksiz gönderiler göndermek

Alternatif olarak mağaza her yeni ürün geldiğinde bütün müşterilerine (spam olarak kabul edilebilecek) on binlerce mail gönderebilir. Bu müşterilerin her gün mağazaya gelmesini engellese de bu ürünlerle ilgilenmeyen müşteriler için sinir bozucu olabilir.

‘Aşağı tükürsem sakal, yukarı tükürsem bıyık’ deyimi ile örtüşen bir durum. Ya müşteri her gün mağazaya gelip zaman ve kaynak harcayacak, ya mağaza müşterilere gereksiz e-postalar gönderip zaman ve kaynak harcayacak.

Çözüm

Bir nesnenin bir durumu ilgileniyorsak bu nesne bizim için ana biz öznedir. Bu öznmize durum değişikliğini başka nesnelere de haber vereceği için yayıncı olarak adlandırmak daha doğru olur. Bu nesnenin durumunu takip etmek isteyen diğer nesneler de bu nesnenin aboneleridir.

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 her ne kadar karmaşık görülse de aslında gayet basit bir süreçtir. Gerçekte süreç şöyle iler; 1) abone nesnelerin referanslarını saklayan bir array alanı 2) bu listeye abone eklemeyi veya çıkartmayı sağlayan bir kaç dışa açık (public) metot.

Observer deseni örnek
Bir abonelik mekanizması nesnelerin olay bildirimlerine abone olmalarını sağlar.

Bu mekanizma ile birlikte herhangi önemli bir olay olduğuna yayıncı tüm abonelerin üzerinden geçerek bu nesnelere özel bildirim metotlarını çağırabilir.

Gerçek dünyada uygulamaların tek bir yayıncı sınıfında gerçekleşen olaylarla ilgilenen onlarca abone sınfıı olabilir. Bütün bunları yayıncının içinde doğrudan belirtip bir bağımlılık oluşturmak istemezsiniz, hatta çoğu zaman bu aboneleri önceden bilemeyebilrisiniz.

Bu nedenle tüm abonelerin, yayıncının kendileri ile 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.

Observer deseni örnekli açıklama
Yayıncı abonelere tümünde ortak olan bildirim metodunu kullanarak haber veriyor.

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 genelde kullanıcı arayüzleri 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.

Observer deseni yayıncının olay bildirimlerini abonelere gönderebilmesini sağlayan bir abonelik arayüzü oluşturur. 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 kullanımlar veya belirli bir süre için izlemesi gereken durumlarda bu deseni kullanabilirsiniz.

Abonelik listesi dinamiktir, böylede aboneler istedikleri zaman abone olur veya abonelikten çıkabilirler.

Diğer tasarım desenleri/kalıpları ile ilişkisi

  • Chain of Responsiblity, 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 potansiyel 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. Bunu nasıl yapabileceğimize bakalım.

    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 var. 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 girebilirler, ama birbirlerinden tamamen farklı kullanımları da vardır.

    Mediator desenini farklı şekillerde uygulayabileceğinize de dikkat edin. Örneğin tüm bileşenleri kalıcı olarak bir mediator nesnesine bağlayabilirsiniz. Bu uygulama Observer’a benzemez ve hala bir Mediator desenidir.

    Şimdi tüm bileşenlerin yayıncı haline geldiği ve birbirileri arasında dinamik bağlantılara izin verilen bir program hayal edin. Burada merkezi bir mediator nesnesi yokken, dağıtık bir observer yapısı olabilir.

Observer Deseni Kod Örnekleri

Örnek PHP Kodu

<?php

namespace RefactoringGuru\Observer\Conceptual;

/**
 * PHP has a couple of built-in interfaces related to the Observer pattern.
 *
 * Here's what the Subject interface looks like:
 *
 * @link http://php.net/manual/en/class.splsubject.php
 *
 *     interface SplSubject
 *     {
 *         // Attach an observer to the subject.
 *         public function attach(SplObserver $observer);
 *
 *         // Detach an observer from the subject.
 *         public function detach(SplObserver $observer);
 *
 *         // Notify all observers about an event.
 *         public function notify();
 *     }
 *
 * There's also a built-in interface for Observers:
 *
 * @link http://php.net/manual/en/class.splobserver.php
 *
 *     interface SplObserver
 *     {
 *         public function update(SplSubject $subject);
 *     }
 */

/**
 * The Subject owns some important state and notifies observers when the state
 * changes.
 */
class Subject implements \SplSubject
{
    /**
     * @var int For the sake of simplicity, the Subject's state, essential to
     * all subscribers, is stored in this variable.
     */
    public $state;

    /**
     * @var \SplObjectStorage List of subscribers. In real life, the list of
     * subscribers can be stored more comprehensively (categorized by event
     * type, etc.).
     */
    private $observers;

    public function __construct()
    {
        $this->observers = new \SplObjectStorage();
    }

    /**
     * The subscription management methods.
     */
    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";
    }

    /**
     * Trigger an update in each subscriber.
     */
    public function notify(): void
    {
        echo "Subject: Notifying observers...\n";
        foreach ($this->observers as $observer) {
            $observer->update($this);
        }
    }

    /**
     * Usually, the subscription logic is only a fraction of what a Subject can
     * really do. Subjects commonly hold some important business logic, that
     * triggers a notification method whenever something important is about to
     * happen (or after it).
     */
    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();
    }
}

/**
 * Concrete Observers react to the updates issued by the Subject they had been
 * attached to.
 */
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";
        }
    }
}

/**
 * The client code.
 */

$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):
    """
    The Subject interface declares a set of methods for managing subscribers.
    """

    @abstractmethod
    def attach(self, observer: Observer) -> None:
        """
        Attach an observer to the subject.
        """
        pass

    @abstractmethod
    def detach(self, observer: Observer) -> None:
        """
        Detach an observer from the subject.
        """
        pass

    @abstractmethod
    def notify(self) -> None:
        """
        Notify all observers about an event.
        """
        pass


class ConcreteSubject(Subject):
    """
    The Subject owns some important state and notifies observers when the state
    changes.
    """

    _state: int = None
    """
    For the sake of simplicity, the Subject's state, essential to all
    subscribers, is stored in this variable.
    """

    _observers: List[Observer] = []
    """
    List of subscribers. In real life, the list of subscribers can be stored
    more comprehensively (categorized by event type, etc.).
    """

    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)

    """
    The subscription management methods.
    """

    def notify(self) -> None:
        """
        Trigger an update in each subscriber.
        """

        print("Subject: Notifying observers...")
        for observer in self._observers:
            observer.update(self)

    def some_business_logic(self) -> None:
        """
        Usually, the subscription logic is only a fraction of what a Subject can
        really do. Subjects commonly hold some important business logic, that
        triggers a notification method whenever something important is about to
        happen (or after it).
        """

        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):
    """
    The Observer interface declares the update method, used by subjects.
    """

    @abstractmethod
    def update(self, subject: Subject) -> None:
        """
        Receive update from subject.
        """
        pass


"""
Concrete Observers react to the updates issued by the Subject they had been
attached to.
"""


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__":
    # The client code.

    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

Yaratımsal Kalıplar (Creational Patterns)

Yapısal Kalıplar (Structural Patterns)

Davranışsal Kalıplar (Behavioral Patterns)