Nesne Tabanlı Programlama 2

11 - Tasarım Desenleri

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2026

Tasarım Desenleri Nedir?

  • Tasarım desenleri, yazılım geliştirmede tekrar eden tasarım problemleri için kullanılan, denenmiş genel çözüm şablonlarıdır.
  • Bir desen, doğrudan kopyalanacak hazır kod parçası değildir.
  • Daha çok şu sorulara cevap verir:
    • Sınıflar birbirleriyle nasıl ilişki kurmalı?
    • Nesne oluşturma sorumluluğu nerede olmalı?
    • Değişmesi muhtemel kısımlar koddan nasıl ayrılmalı?
    • Kod, yeni ihtiyaçlara nasıl daha kolay uyarlanmalı?

Desenleri Neden Öğreniyoruz?

  • Daha düzenli ve anlaşılır kod yazmak için.
  • Sık karşılaşılan problemlere ortak bir bakış geliştirmek için.
  • Kodda değişmesi muhtemel bölümleri daha kontrollü yönetmek için.
  • Diğer yazılımcılarla ortak bir dil kullanmak için.

Örneğin bir geliştirici “burada Observer kullanabiliriz” dediğinde, uzun uzun sınıf ilişkilerini açıklamadan genel yapıyı ifade etmiş olur.

Desenleri Nasıl Düşünmeliyiz?

Bir tasarım desenini öğrenirken yalnızca sınıf yapısına bakmak yeterli değildir.

Önce şu soruya bakmalıyız:

Bu desen hangi değişim ihtiyacını daha yönetilebilir hale getiriyor?

Bu derste her deseni şu akışla ele alacağız:

  1. Karşılaşılan problem
  2. Desenin temel fikri
  3. Basit Python örneği
  4. Kullanırken dikkat edilmesi gereken nokta

Desen Kategorileri

  • Nesne Oluşturma Desenleri (Creational Patterns)
    • Nesnelerin nasıl oluşturulacağıyla ilgilenir.
    • Bugün: Singleton, Factory Method
  • Yapısal Desenler (Structural Patterns)
    • Sınıfların ve nesnelerin nasıl bir araya getirileceğiyle ilgilenir.
    • Bugün: Decorator
  • Davranışsal Desenler (Behavioral Patterns)
    • Nesnelerin birbirleriyle nasıl etkileşime gireceğiyle ilgilenir.
    • Bugün: Observer, Strategy

Singleton Deseni

Problem:

Bazı durumlarda bir sınıftan program boyunca yalnızca bir nesne oluşturulmasını isteriz.

Örneğin:

  • Uygulama ayarlarını tutan nesne
  • Loglama nesnesi
  • Oyun ayarlarını yöneten nesne

Bu nesneden birden fazla oluşturulursa, programın farklı yerlerinde farklı durumlar ortaya çıkabilir.

Singleton Deseni: Temel Fikir

  • Bir sınıftan yalnızca bir tane nesne oluşturulmasını sağlar.
  • Sınıf, kendi tek örneğini kendi içinde saklar.
  • Daha sonra yeni nesne istense bile, var olan aynı nesne döndürülür.
  • Ortak durum tuttuğu için dikkatli kullanılmalıdır.

Yani amaç şudur:

ayarlar1 = Ayarlar()
ayarlar2 = Ayarlar()

print(ayarlar1 is ayarlar2)  # True

İki değişken farklı görünse de aynı nesneyi gösterir.

Singleton: Basit Örnek

class Ayarlar:
    _tek_nesne = None

    def __new__(cls, *args, **kwargs):
        if cls._tek_nesne is None:
            print("Ayarlar ilk kez oluşturuluyor.")
            cls._tek_nesne = super().__new__(cls)
            cls._tek_nesne.tema = "açık"

        return cls._tek_nesne


ayarlar1 = Ayarlar()
print(f"Tema: {ayarlar1.tema}")

ayarlar1.tema = "karanlık"

ayarlar2 = Ayarlar()
print(f"Tema: {ayarlar2.tema}")

print(ayarlar1 is ayarlar2)

Singleton: Kodun Çıktısı

Ayarlar ilk kez oluşturuluyor.
Tema: açık
Tema: karanlık
True

Burada dikkat edilmesi gereken nokta şudur:

  • ayarlar1 ve ayarlar2 iki ayrı nesne değildir.
  • İkisi de bellekteki aynı Ayarlar nesnesini gösterir.
  • ayarlar1.tema değiştirildiğinde, bu değişiklik ayarlar2 üzerinden de görülür.

Singleton Kullanırken Dikkat

Singleton her durumda iyi bir çözüm değildir.

  • Program genelinde ortak durum tuttuğu için test yazmayı zorlaştırabilir.
  • Sınıflar arasında gizli bağımlılıklar oluşturabilir.
  • Gereksiz kullanılırsa kodun esnekliğini azaltabilir.
  • Bazı durumlarda yapılandırma nesnesi veya dependency injection daha uygun olabilir.

Bu nedenle Singleton, “her yerde kullanılacak kolay çözüm” olarak düşünülmemelidir.

Factory Method Deseni

Problem:

Bir sınıf, çalışması için bazı nesneler oluşturmak zorunda olabilir.

Ancak hangi somut nesnenin oluşturulacağı her zaman üst sınıf tarafından bilinmeyebilir.

Örneğin:

  • Bir barınak köpek de oluşturabilir, kedi de oluşturabilir.
  • Genel barınak davranışı aynıdır.
  • Fakat oluşturulacak hayvan türü alt sınıfa göre değişir.

Factory Method Olmadan

Basit bir çözüm olarak if-elif blokları kullanılabilir.

def hayvan_yarat(tur):
    if tur == "kopek":
        return Kopek()
    elif tur == "kedi":
        return Kedi()
    else:
        raise ValueError("Bilinmeyen hayvan türü")

Bu küçük örnekte sorun görünmeyebilir.

Fakat tür sayısı arttıkça:

  • Nesne oluşturma kodu büyür.
  • Yeni türler eklendikçe mevcut kod değişir.
  • Oluşturma kararı tek bir yerde birikmeye başlar.

Basit Factory ile Factory Method Farkı

Basit factory yaklaşımında hangi nesnenin oluşturulacağına genellikle tek bir fonksiyon karar verir.

Factory Method deseninde ise:

  • Üst sınıf genel iş akışını tanımlar.
  • Nesne oluşturma adımını bir metoda bırakır.
  • Oluşturulacak somut nesneye alt sınıf karar verir.

Yani üst sınıf şunu bilir:

Bir hayvan oluşturulacak ve konuşturulacak.

Ama şunu bilmek zorunda değildir:

Bu hayvan köpek mi, kedi mi?

Factory Method: Ürün Arayüzü ve Sınıflar

from abc import ABC, abstractmethod


class IHayvan(ABC):
    @abstractmethod
    def ses_cikar(self):
        pass


class Kopek(IHayvan):
    def ses_cikar(self):
        return "Hav hav!"


class Kedi(IHayvan):
    def ses_cikar(self):
        return "Miyav!"

Factory Method: Üst Sınıf

class HayvanBarinagi(ABC):
    @abstractmethod
    def hayvan_yarat(self) -> IHayvan:
        pass

    def hayvan_konustur(self):
        hayvan = self.hayvan_yarat()
        print(f"Barınak diyor ki: {hayvan.ses_cikar()}")

HayvanBarinagi, hangi hayvanın oluşturulacağını bilmez.

Sadece oluşturulan nesnenin IHayvan arayüzüne uygun olduğunu bilir.

Factory Method: Alt Sınıflar ve Kullanım

class KopekBarinagi(HayvanBarinagi):
    def hayvan_yarat(self) -> IHayvan:
        return Kopek()


class KediBarinagi(HayvanBarinagi):
    def hayvan_yarat(self) -> IHayvan:
        return Kedi()


KopekBarinagi().hayvan_konustur()
KediBarinagi().hayvan_konustur()

Çıktı:

Barınak diyor ki: Hav hav!
Barınak diyor ki: Miyav!

Factory Method: Ne Kazandık?

  • HayvanBarinagi genel akışı bilir.
  • Hangi hayvanın oluşturulacağına alt sınıflar karar verir.
  • Üst sınıf somut sınıflara değil, ortak arayüze bağlı kalır.
  • Yeni bir hayvan türü eklendiğinde yeni bir barınak sınıfı yazılabilir.
  • Üst sınıfın genel davranışı değiştirilmek zorunda kalmaz.
  • Amaç sınıf sayısını artırmak değil, oluşturma kararını üst sınıftan ayırmaktır.

Bu desen özellikle nesne oluşturma kararının değişken olduğu durumlarda kullanışlıdır.

Decorator Deseni

Problem:

Bir nesneye çalışma zamanında yeni özellikler eklemek isteyebiliriz.

Örneğin sade bir kahveye:

  • Süt eklenebilir.
  • Şeker eklenebilir.
  • Hem süt hem şeker eklenebilir.
  • Daha sonra başka eklemeler de yapılabilir.

Bunu her kombinasyon için ayrı sınıf yazarak çözmek iyi bir yaklaşım değildir.

Decorator Olmadan

Her kombinasyon için ayrı sınıf yazmak gerekebilir:

class SadeKahve:
    pass

class SutluKahve:
    pass

class SekerliKahve:
    pass

class SutluSekerliKahve:
    pass

Bu yaklaşım büyüdükçe yönetilmesi zorlaşır.

Yeni bir ekleme geldiğinde sınıf sayısı hızla artabilir.

Decorator Deseni: Temel Fikir

Decorator deseninde:

  • Temel bir nesne vardır.
  • Bu nesne, aynı arayüze sahip başka nesneler tarafından sarılır.
  • Her sarma katmanı nesneye yeni bir davranış veya özellik ekler.
  • Kalıtım ile tüm kombinasyonları üretmek yerine, nesneler iç içe geçirilerek davranış genişletilir.

Örneğin:

SekerEkle
   ↓ sarar
SutEkle
   ↓ sarar
SadeKahve

Decorator: Temel Arayüz ve Nesne

from abc import ABC, abstractmethod


class IKahve(ABC):
    @abstractmethod
    def get_aciklama(self) -> str:
        pass

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


class SadeKahve(IKahve):
    def get_aciklama(self) -> str:
        return "Sade Kahve"

    def get_fiyat(self) -> float:
        return 5.0

Decorator: Temel Dekoratör

class KahveDekoratoru(IKahve):
    def __init__(self, sarilan_kahve: IKahve):
        self._sarilan_kahve = sarilan_kahve

KahveDekoratoru tek başına kullanılmaz.

Bu sınıfın asıl görevi, sarılan kahve nesnesini ortak bir yerde saklamaktır.

Somut dekoratörler, get_aciklama() ve get_fiyat() metodlarını kendilerine göre yazar.

Decorator: Somut Dekoratörler

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

Decorator: Kullanım

kahve1 = SadeKahve()
print(f"{kahve1.get_aciklama()}: {kahve1.get_fiyat()} TL")

kahve2 = SutEkle(SadeKahve())
print(f"{kahve2.get_aciklama()}: {kahve2.get_fiyat()} TL")

kahve3 = SekerEkle(SutEkle(SadeKahve()))
print(f"{kahve3.get_aciklama()}: {kahve3.get_fiyat()} TL")

Çıktı:

Sade Kahve: 5.0 TL
Sade Kahve, Sütlü: 6.5 TL
Sade Kahve, Sütlü, Şekerli: 7.0 TL

Decorator: Ne Kazandık?

  • Her kombinasyon için ayrı sınıf yazmadan yeni özellikler eklenebilir.
  • Özellikler farklı kombinasyonlarla birleştirilebilir.
  • Temel nesnenin kodunu değiştirmeden davranış genişletilebilir.
  • Her dekoratör küçük ve odaklı bir sorumluluk taşır.

Decorator deseninde ana fikir şudur:

Nesneyi değiştirmek yerine, aynı arayüze sahip başka nesnelerle sararız.

Not: Python Decorator ile Karıştırmayalım

Python’daki @decorator sözdizimi ile Decorator tasarım deseni aynı şey değildir.

@decorator
def fonksiyon():
    pass

Python’daki @decorator sözdizimi çoğunlukla fonksiyonları veya sınıfları sarmak için kullanılır.

Bu derste anlattığımız Decorator ise:

  • Nesneleri sararak genişletir.
  • Aynı arayüzü korur.
  • Çalışma zamanında farklı kombinasyonlara izin verir.

Observer Deseni

Problem:

Bir nesnede olay gerçekleştiğinde, birden fazla nesnenin bundan haberdar olması gerekebilir.

Örneğin bir düğmeye tıklandığında:

  • Log kaydı tutulabilir.
  • Ekran yenilenebilir.
  • Bildirim gönderilebilir.

Düğme sınıfının tüm bu ayrıntıları bilmesi doğru bir tasarım olmayabilir.

Observer Olmadan

Düğme sınıfı doğrudan farklı işleri yapmak zorunda kalabilir.

class Dugme:
    def tikla(self):
        print("Düğmeye tıklandı.")
        print("Log kaydı tutuldu.")
        print("Ekran yenilendi.")
        print("Bildirim gönderildi.")

Bu durumda Dugme sınıfı yalnızca tıklanma olayını temsil etmez.

Aynı zamanda loglama, ekran güncelleme ve bildirim gibi ayrıntıları da bilmeye başlar.

Observer Deseni: Temel Fikir

Observer deseninde iki temel taraf vardır:

  • Subject
    • Olayın gerçekleştiği nesnedir.
    • Kendisini dinleyen observer nesnelerini tutar.
  • Observer
    • Subject üzerinde bir değişiklik olduğunda haberdar edilen nesnedir.

Subject, observer nesnelerinin iç işleyişini bilmez.

Sadece onlara haber verir.

Observer: Observer Arayüzü

from abc import ABC, abstractmethod


class IDinleyici(ABC):
    @abstractmethod
    def guncelle(self):
        pass

Observer nesneleri ortak bir arayüzü uygular.

Böylece subject, dinleyicilerin somut sınıflarını bilmek zorunda kalmaz.

Observer: Observer Sınıfları

class LogKaydedici(IDinleyici):
    def guncelle(self):
        print("Log: Düğmeye tıklandı.")


class EkranYenileyici(IDinleyici):
    def guncelle(self):
        print("Ekran: Arayüz güncelleniyor.")


class BildirimGonderici(IDinleyici):
    def guncelle(self):
        print("Bildirim: Kullanıcıya haber veriliyor.")

Observer: Subject Sınıfı

class Dugme:
    def __init__(self):
        self._dinleyiciler = []

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

    def abone_cikar(self, dinleyici):
        self._dinleyiciler.remove(dinleyici)

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

    def _bildir(self):
        for dinleyici in self._dinleyiciler:
            dinleyici.guncelle()

Gerçek uygulamalarda dinleyiciler daha sonra listeden çıkarılabilir.

Bu nedenle Observer yapılarında genellikle hem abone ekleme hem de abonelikten çıkarma işlemi bulunur.

Observer: Kullanım

kaydet_butonu = Dugme()

logcu = LogKaydedici()
ekranci = EkranYenileyici()
bildirimci = BildirimGonderici()

kaydet_butonu.abone_ekle(logcu)
kaydet_butonu.abone_ekle(ekranci)
kaydet_butonu.abone_ekle(bildirimci)

kaydet_butonu.tikla()

Çıktı:

Düğmeye tıklandı.
Log: Düğmeye tıklandı.
Ekran: Arayüz güncelleniyor.
Bildirim: Kullanıcıya haber veriliyor.

Observer: Ne Kazandık?

  • Dugme, loglama, ekran yenileme veya bildirim ayrıntılarını bilmez.
  • Yeni bir dinleyici eklemek için Dugme sınıfını değiştirmek gerekmez.
  • Bir olaydan birden fazla nesne haberdar edilebilir.
  • Olayı üreten nesne ile olaya tepki veren nesneler ayrılmış olur.
  • Gerektiğinde dinleyiciler abonelikten çıkarılabilir.

Observer deseninde temel fikir şudur:

Olayı üreten nesne, olaya kimlerin nasıl tepki verdiğini bilmek zorunda değildir.

Strategy Deseni

Problem:

Bir işi yapmanın birden fazla yolu olabilir.

Örneğin bir kurye paketi farklı yöntemlerle taşıyabilir:

  • Yürüyerek
  • Bisikletle
  • Arabayla

Bu yöntemleri tek bir sınıf içinde if-elif bloklarıyla yönetmek zamanla kodu büyütebilir.

Strategy Olmadan

class Kurye:
    def paket_gonder(self, yontem):
        print("Paket gönderiliyor...")

        if yontem == "yurume":
            print("Yürüyerek taşınıyor.")
        elif yontem == "bisiklet":
            print("Bisikletle taşınıyor.")
        elif yontem == "araba":
            print("Arabayla taşınıyor.")

Bu yapı küçük örneklerde çalışır.

Fakat yeni taşıma yöntemleri geldikçe Kurye sınıfı sürekli değişmek zorunda kalır.

Strategy Deseni: Temel Fikir

Strategy deseninde:

  • Bir işi yapmanın farklı yolları ayrı sınıflara taşınır.
  • Bu sınıflar ortak bir arayüzü uygular.
  • Context sınıfı, işi doğrudan yapmak yerine seçilen strategy nesnesine devreder.

Bu örnekte:

  • Kurye context sınıfıdır.
  • Taşıma yöntemleri strategy sınıflarıdır.

Strategy: Strategy Arayüzü ve Sınıflar

from abc import ABC, abstractmethod


class ITasimaYontemi(ABC):
    @abstractmethod
    def tasi(self):
        pass


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


class BisikletYontemi(ITasimaYontemi):
    def tasi(self):
        print("Bisikletle taşınıyor.")


class ArabaYontemi(ITasimaYontemi):
    def tasi(self):
        print("Arabayla taşınıyor.")

Strategy: Context Sınıfı

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()

Kurye, taşımanın nasıl yapılacağını bilmez.

Sadece seçilen yöntemin tasi() metodunu çağırır.

Strategy: Kullanım

yurume = YurumeYontemi()
bisiklet = BisikletYontemi()
araba = ArabaYontemi()

kurye = Kurye(yurume)
kurye.paket_gonder()

kurye.yontem_degistir(bisiklet)
kurye.paket_gonder()

kurye.yontem_degistir(araba)
kurye.paket_gonder()

Çıktı:

Paket gönderiliyor...
Yürüyerek taşınıyor.
Paket gönderiliyor...
Bisikletle taşınıyor.
Paket gönderiliyor...
Arabayla taşınıyor.

Strategy: Ne Kazandık?

  • Taşıma yöntemleri Kurye sınıfının dışına alınmış olur.
  • if-elif blokları context sınıfından çıkarılmış olur.
  • Yeni yöntem eklemek için yeni bir strategy sınıfı yazılabilir.
  • Context sınıfının kodu daha sade kalır.
  • Çalışma zamanında yöntem değiştirilebilir.

Strategy deseninde temel fikir şudur:

Değişebilen algoritmayı ayrı sınıfa taşı ve gerektiğinde değiştirilebilir hale getir.

Desenleri Karşılaştırma

Desen Değişen Kısım Çözüm Fikri
Singleton Nesne sayısı Tek örnek kullanılır
Factory Method Oluşturulacak nesne türü Karar alt sınıfa bırakılır
Decorator Ek özellikler Nesne aynı arayüzle sarılır
Observer Olay tepkileri Abonelere haber verilir
Strategy Algoritma veya yöntem Ayrı sınıfa taşınır

Hangi Deseni Ne Zaman Düşünebiliriz?

  • Program boyunca tek bir ortak nesne gerekiyorsa:
    • Singleton
  • Üst sınıf genel akışı bilsin, oluşturulacak nesneye alt sınıf karar versin istiyorsak:
    • Factory Method
  • Bir nesneye çalışma zamanında yeni özellikler eklemek istiyorsak:
    • Decorator
  • Bir olay olduğunda birden fazla nesne haberdar edilecekse:
    • Observer
  • Bir işi yapmanın farklı yolları varsa ve bu yollar değiştirilebilir olmalıysa:
    • Strategy

Özet

Bu derste beş tasarım desenini gördük:

  • Singleton
    • Tek nesne garantisi sağlar.
  • Factory Method
    • Nesne oluşturma kararını alt sınıflara bırakır.
  • Decorator
    • Nesneleri sararak yeni özellikler ekler.
  • Observer
    • Bir olay olduğunda ilgili nesnelere haber verir.
  • Strategy
    • Bir işi yapmanın farklı yollarını değiştirilebilir hale getirir.

Alıştırma 1: Hangi Desen?

Bir metin editöründe yazıya kalın, italik veya altı çizili gibi biçimler eklemek istiyorsunuz.

Bu biçimleri metnin orijinal içeriğini değiştirmeden, metni sararak uygulamak ve birden fazla biçimi birleştirmek istiyorsunuz.

Hangi desen daha uygundur?

    1. Factory Method
    1. Decorator
    1. Singleton

Alıştırma 2: Hangi Desen?

Bir online oyunda oyuncu başarı kazandığında:

  • Profil sayfası güncellenecek.
  • Arkadaş listesine bildirim gönderilecek.
  • Log kaydı tutulacak.

Başarı kazanma olayını bu işlemlerden ayırmak istiyorsunuz.

Hangi desen daha uygundur?

    1. Strategy
    1. Singleton
    1. Observer

Alıştırma 3: Hangi Desen?

Bir uygulamada raporlar farklı biçimlerde dışa aktarılabiliyor:

  • PDF
  • Excel
  • CSV

Kullanıcı çalışma zamanında dışa aktarma yöntemini seçebiliyor.

Hangi desen daha uygundur?

    1. Strategy
    1. Decorator
    1. Singleton

Alıştırma 4: Hangi Desen?

Bir üst sınıf, belge açma sürecinin genel akışını biliyor.

Ancak açılacak belgenin türüne göre farklı belge nesneleri oluşturulması gerekiyor:

  • PDF belgesi
  • Word belgesi
  • Metin belgesi

Nesne oluşturma kararını alt sınıflara bırakmak istiyorsunuz.

Hangi desen daha uygundur?

    1. Factory Method
    1. Observer
    1. Decorator

Alıştırma 5: Kod İnceleme

Aşağıdaki kodda yeni ödeme yöntemi eklendikçe OdemeServisi sınıfı değişmek zorunda kalıyor.

class OdemeServisi:
    def odeme_yap(self, yontem):
        if yontem == "kredi_karti":
            print("Kredi kartı ile ödeme")
        elif yontem == "havale":
            print("Havale ile ödeme")
        elif yontem == "dijital_cuzdan":
            print("Dijital cüzdan ile ödeme")

Bu yapı hangi desenle daha esnek hale getirilebilir?

    1. Singleton
    1. Strategy
    1. Observer

Alıştırmalar: Cevaplar

Alıştırma Cevap
1 B) Decorator
2 C) Observer
3 A) Strategy
4 A) Factory Method
5 B) Strategy

Kapanış

Tasarım desenleri, yazılımda tekrar eden tasarım problemlerine verilen genel çözüm yaklaşımlarıdır.

Önemli olan desen adlarını ezberlemek değildir.

Asıl önemli olan şudur:

  • Kodda hangi problem var?
  • Değişmesi muhtemel kısım nerede?
  • Sorumluluklar doğru sınıflara ayrılmış mı?
  • Bu desen gerçekten gerekli mi?

Okuma Önerisi

https://refactoring.guru/design-patterns