11 - Tasarım Desenleri
2026
Ö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.
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:
Problem:
Bazı durumlarda bir sınıftan program boyunca yalnızca bir nesne oluşturulmasını isteriz.
Örneğin:
Bu nesneden birden fazla oluşturulursa, programın farklı yerlerinde farklı durumlar ortaya çıkabilir.
Yani amaç şudur:
İki değişken farklı görünse de aynı nesneyi gösterir.
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)Burada dikkat edilmesi gereken nokta şudur:
ayarlar1 ve ayarlar2 iki ayrı nesne değildir.Ayarlar nesnesini gösterir.ayarlar1.tema değiştirildiğinde, bu değişiklik ayarlar2 üzerinden de görülür.Singleton her durumda iyi bir çözüm değildir.
Bu nedenle Singleton, “her yerde kullanılacak kolay çözüm” olarak düşünülmemelidir.
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:
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:
Basit factory yaklaşımında hangi nesnenin oluşturulacağına genellikle tek bir fonksiyon karar verir.
Factory Method deseninde ise:
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?
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.
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ı:
HayvanBarinagi genel akışı bilir.Bu desen özellikle nesne oluşturma kararının değişken olduğu durumlarda kullanışlıdır.
Problem:
Bir nesneye çalışma zamanında yeni özellikler eklemek isteyebiliriz.
Örneğin sade bir kahveye:
Bunu her kombinasyon için ayrı sınıf yazarak çözmek iyi bir yaklaşım değildir.
Her kombinasyon için ayrı sınıf yazmak gerekebilir:
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 deseninde:
Örneğin:
class KahveDekoratoru(IKahve):
def __init__(self, sarilan_kahve: IKahve):
self._sarilan_kahve = sarilan_kahveKahveDekoratoru 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.
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.5kahve1 = 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ı:
Decorator deseninde ana fikir şudur:
Nesneyi değiştirmek yerine, aynı arayüze sahip başka nesnelerle sararız.
Python’daki @decorator sözdizimi ile Decorator tasarım deseni aynı şey değildir.
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:
Problem:
Bir nesnede olay gerçekleştiğinde, birden fazla nesnenin bundan haberdar olması gerekebilir.
Örneğin bir düğmeye tıklandığında:
Düğme sınıfının tüm bu ayrıntıları bilmesi doğru bir tasarım olmayabilir.
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 deseninde iki temel taraf vardır:
Subject, observer nesnelerinin iç işleyişini bilmez.
Sadece onlara haber verir.
Observer nesneleri ortak bir arayüzü uygular.
Böylece subject, dinleyicilerin somut sınıflarını bilmek zorunda kalmaz.
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.
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ı:
Dugme, loglama, ekran yenileme veya bildirim ayrıntılarını bilmez.Dugme sınıfını değiştirmek gerekmez.Observer deseninde temel fikir şudur:
Olayı üreten nesne, olaya kimlerin nasıl tepki verdiğini bilmek zorunda değildir.
Problem:
Bir işi yapmanın birden fazla yolu olabilir.
Örneğin bir kurye paketi farklı yöntemlerle taşıyabilir:
Bu yöntemleri tek bir sınıf içinde if-elif bloklarıyla yönetmek zamanla kodu büyütebilir.
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 deseninde:
Bu örnekte:
Kurye context sınıfıdır.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.")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.
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ı:
Kurye sınıfının dışına alınmış olur.if-elif blokları context sınıfından çıkarılmış olur.Strategy deseninde temel fikir şudur:
Değişebilen algoritmayı ayrı sınıfa taşı ve gerektiğinde değiştirilebilir hale getir.
| 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 |
Bu derste beş tasarım desenini gördük:
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?
Bir online oyunda oyuncu başarı kazandığında:
Başarı kazanma olayını bu işlemlerden ayırmak istiyorsunuz.
Hangi desen daha uygundur?
Bir uygulamada raporlar farklı biçimlerde dışa aktarılabiliyor:
Kullanıcı çalışma zamanında dışa aktarma yöntemini seçebiliyor.
Hangi desen daha uygundur?
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:
Nesne oluşturma kararını alt sınıflara bırakmak istiyorsunuz.
Hangi desen daha uygundur?
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?
| Alıştırma | Cevap |
|---|---|
| 1 | B) Decorator |
| 2 | C) Observer |
| 3 | A) Strategy |
| 4 | A) Factory Method |
| 5 | B) Strategy |
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: