Nesne Tabanlı Programlama 2

11 - Tasarım Desenleri

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2025

Giriş: Tasarım Desenleri Nedir?

  • Tasarım Desenleri: Yazılım yaparken sık sık karşılaştığımız sorunlara denenmiş, hazır çözüm yollarıdır.
  • Bunlar yemek tarifleri gibidir: Bize neyi nasıl yapacağımızı gösterir ama tam olarak aynı malzemeleri kullanmak zorunda değiliz.
  • Neden öğreniyoruz?
    • Daha düzenli, anlaşılır kod yazmak için.
    • Sorunlara daha hızlı çözüm bulmak için.
    • Diğer yazılımcılarla ortak bir dil konuşmak için.

Desen Kategorileri (Kısaca)

  • Yaratımsal (Creational): Nesneleri nasıl oluşturacağımızla ilgilenir.
    • Bugün: Singleton, Factory Method
  • Yapısal (Structural): Sınıfları ve nesneleri nasıl bir araya getireceğimizle, nasıl daha büyük yapılar kuracağımızla ilgilenir.
    • Bugün: Decorator
  • Davranışsal (Behavioral): Nesnelerin birbirleriyle nasıl konuşacağı ve iş yapacağıyla ilgilenir.
    • Bugün: Observer, Strategy

Yaratımsal: Singleton Deseni

  • Amaç: Bir sınıftan sadece bir tane nesne üretildiğinden emin olmak.
  • Ne Zaman Kullanılır? Bütün programda tek olması gereken şeyler için: Oyun ayarları, Veritabanı bağlantısı, Loglama nesnesi.
  • Nasıl Çalışır? Sınıf, kendi tek örneğini bir yerde saklar. İstendiğinde hep o sakladığı örneği verir.

Singleton: Basit Örnek

class Ayarlar:
    _tek_nesne = None

    def __new__(cls): # Nesne yaratılmadan önce bu çalışır
        if cls._tek_nesne is None: # Daha önce yaratılmadıysa...
            print("Ayarlar ilk kez yaratılıyor.")
            cls._tek_nesne = super().__new__(cls) # Yeni nesne yarat
            cls._tek_nesne.tema = "açık"
        return cls._tek_nesne # Hep saklanan nesneyi döndür

# Kullanım
ayarlar1 = Ayarlar()
print(f"Tema: {ayarlar1.tema}") # açık
ayarlar1.tema = "karanlık"
ayarlar2 = Ayarlar()
print(f"Tema: {ayarlar2.tema}") # karanlık
print(f"Aynı nesne mi? {ayarlar1 is ayarlar2}") # True

İlk seferde nesne yaratılır, sonrakilerde hep aynı nesne döndürülür.

Yaratımsal: Factory Method Deseni

  • Amaç: Ne tür bir nesne yaratılacağına karar verme işini alt sınıflara bırakmak.
  • Ne Zaman Kullanılır? Farklı durumlara göre farklı nesneler yaratmak istediğimizde.
  • Nasıl Çalışır? Bir “fabrika” metodu tanımlanır. Alt sınıflar bu metodu kullanarak kendi istedikleri ürünü üretirler.
  • Analoji: Oyuncak fabrikası “oyuncak yap” der, araba fabrikası araba yapar, bebek fabrikası bebek yapar.

Factory Method: Basit Örnek

# Ürünler
class Kopek:
    def ses_cikar(self): return "Hav hav!"
class Kedi:
    def ses_cikar(self): return "Miyav!"

# Yaratıcı (Fabrika) Arayüzü/Soyut Sınıfı
from abc import ABC, abstractmethod
class HayvanBarinagi(ABC):
    @abstractmethod
    def hayvan_yarat(self): pass # Factory Method

    def hayvan_konustur(self): # Factory Method'u kullanır
        hayvan = self.hayvan_yarat()
        print(f"Barınak diyor ki: {hayvan.ses_cikar()}")

# Somut Yaratıcılar (Ne üreteceğini bilir)
class KopekBarinagi(HayvanBarinagi):
    def hayvan_yarat(self): return Kopek()
class KediBarinagi(HayvanBarinagi):
    def hayvan_yarat(self): return Kedi()

# Kullanım
KopekBarinagi().hayvan_konustur() # Hav hav!
KediBarinagi().hayvan_konustur() # Miyav!

Üst sınıf (HayvanBarinagi), hangi hayvanın yaratıldığını bilmez.

Yapısal: Decorator Deseni

  • Amaç: Bir nesneye, onu sarmalayarak (wrapping), dinamik olarak (çalışma zamanında) yeni sorumluluklar veya davranışlar eklemek.
  • Ne Zaman Kullanılır?
    • Bir nesnenin işlevselliğini, alt sınıf oluşturmadan genişletmek istediğimizde.
    • Bir nesneye eklenen özelliklerin farklı kombinasyonları gerektiğinde.
  • Nasıl Çalışır? Temel bir nesne (component) ve onu saran bir veya daha fazla “dekoratör” (decorator) sınıfı vardır. Dekorator, hem temel nesneyle aynı arayüze sahiptir hem de sardığı nesneye bir referans tutar. Kendi işini yapar ve/veya işi sardığı nesneye devreder.
  • Analoji: Sade bir kahveye (component) önce süt ekleyici (decorator), sonra şeker ekleyici (decorator) sarmalayarak farklı kahveler elde etmek.

Decorator: Basit Örnek (Kahve)

from abc import ABC, abstractmethod

# 1. Component Arayüzü (Temel Nesne)
class IKahve(ABC):
    @abstractmethod
    def get_aciklama(self) -> str: pass
    @abstractmethod
    def get_fiyat(self) -> float: pass

# 2. Concrete Component (Somut Temel Nesne)
class SadeKahve(IKahve):
    def get_aciklama(self) -> str: return "Sade Kahve"
    def get_fiyat(self) -> float: return 5.0

# 3. Decorator Soyut Sınıfı (Hem IKahve hem de bir IKahve referansı tutar)
class KahveDekoratoru(IKahve, ABC):
    def __init__(self, sarilan_kahve: IKahve):
        self._sarilan_kahve = sarilan_kahve

    @abstractmethod
    def get_aciklama(self) -> str: pass
    @abstractmethod
    def get_fiyat(self) -> float: pass

# 4. Concrete Decorators (Somut Dekorlar - Yeni özellik eklerler)
class SutEkle(KahveDekoratoru):
    def get_aciklama(self) -> str:
        return self._sarilan_kahve.get_aciklama() + ", Sütlü"
    def get_fiyat(self) -> float:
        return self._sarilan_kahve.get_fiyat() + 1.5

class SekerEkle(KahveDekoratoru):
    def get_aciklama(self) -> str:
        return self._sarilan_kahve.get_aciklama() + ", Şekerli"
    def get_fiyat(self) -> float:
        return self._sarilan_kahve.get_fiyat() + 0.5

# Kullanım
kahve1 = SadeKahve()
print(f"{kahve1.get_aciklama()}: {kahve1.get_fiyat()} TL")

kahve2 = SutEkle(SadeKahve()) # Sade kahveyi süt ile sar
print(f"{kahve2.get_aciklama()}: {kahve2.get_fiyat()} TL")

kahve3 = SekerEkle(SutEkle(SadeKahve())) # Sütlü kahveyi şeker ile sar
print(f"{kahve3.get_aciklama()}: {kahve3.get_fiyat()} TL")

Dekoratörler, nesneyi katman katman sararak yeni özellikler ekler.

Davranışsal: Observer Deseni

  • Amaç: Bir nesnede (Subject) bir değişiklik olduğunda, ona bağımlı diğer nesnelere (Observer) otomatik olarak haber vermek.
  • Ne Zaman Kullanılır? Bir olay olduğunda birden fazla yerin etkilenmesi gerektiğinde (Örn: Buton tıklanınca arayüzün güncellenmesi, Fiyat değişince sepetin güncellenmesi).
  • Nasıl Çalışır? Subject (olayı yapan), Observer’ları (olayı dinleyenleri) listeler. Durumu değişince listedeki herkese haber verir.
  • Analoji: Youtube kanalına (Subject) abone (Observer) olursunuz. Yeni video gelince size bildirim gelir.

Observer: Basit Örnek

# Subject (Olayı yayınlayan)
class Dugme:
    def __init__(self):
        self._dinleyiciler = [] # Abone olanlar

    def tikla(self):
        print("\nDüğmeye tıklandı!")
        self._bildir()

    def abone_ekle(self, dinleyici):
        self._dinleyiciler.append(dinleyici)

    def _bildir(self):
        for dinleyici in self._dinleyiciler:
            dinleyici.guncelle() # Her dinleyiciye haber ver

# Observer (Olayı dinleyen) - Arayüz (İsteğe bağlı ama iyi pratik)
from abc import ABC, abstractmethod
class IDinleyici(ABC):
    @abstractmethod
    def guncelle(self): pass

# Concrete Observers
class LogKaydedici(IDinleyici):
    def guncelle(self): print("  -> Log: Düğmeye tıklandı.")
class EkranYenileyici(IDinleyici):
    def guncelle(self): print("  -> Ekran: Arayüz güncelleniyor.")

# Kullanım
kaydet_butonu = Dugme()
logcu = LogKaydedici()
ekranci = EkranYenileyici()

kaydet_butonu.abone_ekle(logcu) # Logcu butonu dinliyor
kaydet_butonu.abone_ekle(ekranci) # Ekrancı butonu dinliyor

kaydet_butonu.tikla()

Düğme, kimin dinlediğini bilmez, sadece guncelle metodunu çağırır.

Davranışsal: Strategy Deseni

  • Amaç: Bir işi yapmanın farklı yollarını (algoritmaları) ayrı sınıflar halinde paketlemek ve bunları birbirinin yerine kullanılabilir yapmak.
  • Ne Zaman Kullanılır? Bir görevi yapmanın birden fazla yöntemi varsa (farklı sıralama, farklı sıkıştırma) ve bunlar arasında kolayca geçiş yapmak istiyorsak.
  • Nasıl Çalışır? Bir Context (işi yaptıran) sınıfı, hangi Strategy (işi yapma yöntemi) sınıfını kullanacağını bilir ve işi ona devreder.
  • Analoji: Navigasyonun (Context) farklı rota stratejileri (en kısa, en hızlı) olması.

Strategy: Basit Örnek

from abc import ABC, abstractmethod

# Strategy Arayüzü
class ITasimaYontemi(ABC):
    @abstractmethod
    def tasi(self): pass

# Concrete Strategies
class YurumeYontemi(ITasimaYontemi):
    def tasi(self): print("Yürüyerek taşınıyor.")
class BisikletYontemi(ITasimaYontemi):
    def tasi(self): print("Bisikletle taşınıyor.")

# Context (Stratejiyi kullanan)
class Kurye:
    def __init__(self, yontem: ITasimaYontemi):
        self._yontem = yontem

    def yontem_degistir(self, yeni_yontem: ITasimaYontemi):
        self._yontem = yeni_yontem

    def paket_gonder(self):
        print("Paket gönderiliyor...")
        self._yontem.tasi() # İşi seçilen yönteme devret

# Kullanım
yurume = YurumeYontemi()
bisiklet = BisikletYontemi()

kurye = Kurye(yurume) # Başlangıçta yürüyerek
kurye.paket_gonder()

kurye.yontem_degistir(bisiklet) # Yöntemi değiştir
kurye.paket_gonder()

Kurye, taşıma işinin nasıl* yapıldığını bilmez, sadece yöntemi kullanır.*

Özet: Desenler Ne İşe Yarar?

  • Tekrar Eden Sorunlara Hazır Çözümler: Denenmiş yolları kullanırız.
  • Daha İyi Kod: Esnek, anlaşılır, bakımı kolay kod yazarız (Genellikle SOLID’e uyar).
  • Ortak Dil: Yazılımcılar arasında anlaşmayı kolaylaştırır.
  • Bugün Gördüklerimiz:
    • Singleton: Tek nesne garantisi.
    • Factory Method: Nesne üretimini alt sınıflara bırakma.
    • Decorator: Nesnelere dinamik olarak özellik ekleme/sarma.
    • Observer: Değişiklikleri otomatik haber verme.
    • Strategy: Algoritmaları değiştirilebilir yapma.

Alıştırma 1: Hangi Desen? (Basit)

Bir metin editöründe yazdığınız metne kalın, italik veya altı çizili gibi formatlar eklemek istiyorsunuz. Bu formatları, metnin orijinal içeriğini değiştirmeden, sanki metni sararak uygulamak ve hatta birden fazla formatı (örn: hem kalın hem italik) birleştirmek için hangi yapısal desen uygun olur?

    1. Factory Method
    1. Decorator
    1. Singleton

Alıştırma 2: Hangi Desen? (Basit)

Bir online oyun geliştiriyorsunuz. Oyuncuların başarı (achievement) kazandığında, hem oyuncunun profil sayfasının güncellenmesi hem de arkadaş listesine bildirim gönderilmesi gerekiyor. Başarı kazanma olayını (Subject), profil ve arkadaş listesi güncellemelerinden (Observer) ayırmak için hangi davranışsal desen mantıklıdır?

    1. Strategy
    1. Singleton
    1. Observer

Alıştırma 3: Hangi Desen? (Basit)

Uygulamanızda, kullanıcıların farklı veritabanı türlerine (MySQL, PostgreSQL, SQLite) bağlanabilmesi gerekiyor. Veritabanı bağlantısını oluşturan kodu, ana iş mantığınızdan ayırmak ve hangi veritabanı bağlantısının oluşturulacağına karar verme işini özelleştirmek için hangi yaratımsal desen kullanılabilir?

    1. Strategy
    1. Factory Method
    1. Decorator