State Tasarım Deseni Nedir?

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.

State Tasarım Deseninin Amacı

State bir nesnenin iç durumu değiştiğinde davranışını da değiştirmesini sağlayan bir tasarım desenidir. Sanki nesne sınıfını (class) değiştirmiş gibi görünür.

Sorun

State deseni sonlu durum makineleri konsepti ile yakından ilişkilidir.

Sonlu Durum Makinesi

Ana fikir şu; herhangi bir zamanda, bir programın olabileceği sınırlı sayıda durum vardır. Herhangi bir benzersiz durumda, program farklı şekilde hareket eder ve program durumu birinden diğerine anlık olarak değişebilir. Fakat, programın mevcut duruma bağlı olarak, durumu bir başka duruma değiştirilebilir veya değiştirilemeyebilir. Bu değiştirme kurallarına geçişler denir ve bunlar önceden belirlidir ve bir sonlu sayıdadır.

Bu yaklaşımı nesnelere de uygulayabilirsiniz. Bir Document sınıfınız olduğunu düşünün. Bir doküman şu 3 durumdan birinde olabilir: Draft (Taslak), Moderation (Moderasyonda) ve Published (Yayımlanmış). Dokümanın publish (yayınla) metodu her durumda farklı çalışır:

  • Taslak durumundayken yayımlanırsa, moderasyon durumuna geçer
  • Moderasyon durumundayken dokümanı genele açık hale getirir, fakat bunu sadece mevcut kullanıcı bir yöneticiyse yapabilir.
  • Daha önceden yayımlanmış bir doküman için durumda bir değişiklik olmaz.
Bir doküman nesnesi için mümkün olan durumlar ve geçişler

State machines (durum makineleri) genel olarak, mevcut duruma göre uygun davranışı seçen koşullu ifadelerle ( if veya switch gibi) uygulanırlar. Genelde durum nesnenin alanlarından bazılarının değerlerinden oluşur. Sonlu-durum-makinelerini daha önce duymadıysanız bile, muhtemelen en az bir defa bir durum uygulaması yapmışsınızdır. Aşağıdaki kod yapısı tanıdık geldi mi?

Koşullu ifadelere dayalı bir durum makinesinin zayıf noktası yeni durumlar ve duruma bağlı davranışlar eklemek istediğinizde ortaya çıkar. Çoğu metot, mevcut duruma göre doğru davranışı seçen devasa koşul ifadeleri içerecektir. Geçiş mantığında yapılacak herhangi bir değişiklik tüm metodlardaki koşullu ifadelerin değişmesini gerektireceğinden bu tarz kodları yönetmek zordur.

Proje geliştikçe sorun daha da büyür. Bütün olası durumları ve geçişleri tasarım aşamasında planlamak hiç de kolay değildir. Böyle olunca da başta çok yalın gözüken bir durum makinesi kodu, zamanla şişerek karmaşık bir hale dönüşebilir.

Çözüm

State deseni bir nesnenin tüm olası durumları için bir sınıf oluşturmanızı ve bu duruma özel davranışların hepsini bu özel sınıf içerisine aktarmanızı tavsiye eder.

Bağlam adı verilen orijinal nesne tüm davranışları kendisi uygulayacağına, her biri kendi durumunu tutan durum nesnelerinin referansını saklayarak, durumla alakalı işleri gerektiğinde onlara delege eder.

Doküman işi bir durum nesnesine delege ediyor.

Bağlamın bir başka duruma geçiş yapması için, aktif durum nesnesi yeni durumu temsil eden bir başka nesne ile değiştirilir. Bunun mümkün olması için tüm durum nesnelerinin aynı arayüzü esas alması şarttır. Böylece bağlam nesnesi durum nesneleri ile bu arayüz üzerinden iletişim kurabilir.

Bu yapı Strategy desenine benzer görünebilir, fakat ikisi arasında önemli bir fark var. State deseninde, durumlar birbirlerinden haberdar olabilir ve bir durumdan diğerine geçişi başlatabilir

Uygulanabilirlik

Mevcut durumuna bağlı olarak farklı hareket etmesi gereken nesneleriniz varsa, olası durum sayıları çok fazlaysa ve bu durumlara özel kodlar çok sık değişiyorsa State desenini kullanabilirsiniz.

Bu desen duruma özel tüm kodları ayrı sınıflara almanızı tavsiye eder. Böylece, diğerlerinden bağımsız olarak yeni durumlar ekleyebilir veya mevcut durumları değiştirebilirsiniz. Böylelikle kodu yönetme maliyetiniz azalır.

Sınıfın belirli alanlarının değerlerine göre hareketlerini değiştirmek için kullanılan aşırı miktarda koşullu ifadelerle dolu, kirli bir sınıfınız varsa bu deseni kullanabilirsiniz.

State deseni bu koşullu ifadeleri ilgili durum sınıflarına dönüştürmenizi sağlar. Bunu yaparken ana sınıfınızda durum yönetimi için kullandığınız geçici alanları ve yardımcı metotları temizleyebilirsiniz.

Koşullu ifadeler esaslı bir durum makinesinde, farklı durumlar ve geçişler için tekrarlanarak kullanılan çok fazla kodunuz varsa bu deseni kullanabilirsiniz.

State deseni durum, sınıfları için bir hiyerarşi oluşturmanızı ve ortak kodları temel bir abstract sınıfı içine alarak tekrarları azaltmanızı sağlar.

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

  • Bridge, State, Strategy (ve bir dereceye kadar Adapter) çok benzer yapıya sahiptir. Aslında, bu desenlerin hepsi kompozisyon, yani işi başka nesnelere delege etme esasına dayanır. Fakat, her biri farklı bir soruna çözüm üretirler. Desenler kodunuzu belirli bir yolla oluşturmak için kesin tarifler değildir.
  • State Strategy ‘nin bir uzantısı olarak düşünülebilir. Her iki desende kompozisyon esaslıdır: ikisi de işin bir bölümünü yardımcı nesnelere delege ederek bağlamın davranışını değiştirirler. Strategy nesneleri birbirinden tamamen bağımsız ve hatta bi haber hale getirir. Öte yandan State durumlar arasında bağımlılıklar olmasına sınır getirmez ve istedikleri zaman bağlam nesnesinin durumunu değiştirmelerine olanak verir.

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)

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

Bu yazıyı beğendiniz mi?
Beğen
Bana sosyal medya hesaplarımdan ulaşın

Evren Bal

Merhaba Dünya. Teknoloji delisi, farklı düşünmeyi seven, 1996'dan beri Internet canlısı, garajdan başarı hikayesi çıkartmayı becerememiş, çünkü garajdan hiç çıkmayan bir insanım. :) Bana buradan veya sosyal medyadan ulaşmanızı, birlikte her konuda fikir alışverişi ve beyin jimnastiği yapmayı heyecanla bekliyorum.

1 Yanıt

  1. 8 Ekim 2021

    […] Command Iterator Mediator Memento Observer State Strategy Template Method […]

Soru ve Önerileriniz?

%d blogcu bunu beğendi: