Design Patterns: Singleton, Factory, Observer
Bir mimar düşün. Her bina farklıdır ama bazı yapısal çözümler hep tekrarlanır: kemer, kubbe, kolon... Bunlar yüzyılların deneyimiyle "kanıtlanmış" çözümlerdir. Biri "nasıl geniş bir alanı desteklerim?" diye sorduğunda, sıfırdan deney yapmazsın — kubbe kullanırsın.
Yazılımda da aynı. Design pattern'ler, yazılım geliştirmede tekrar eden sorunlara tekrar tekrar işe yaradığı kanıtlanmış çözümlerdir. Gang of Four (GoF) kitabı 1994'te 23 pattern tanımladı. Ama Python'da bunların çoğu gereksiz veya çok daha basit çözülebilir.
Bu derste Python'a en uygun pattern'leri, gerçek örneklerle ve Python'un güçlü yanlarını kullanarak öğreneceğiz.
Pattern'lere Neden İhtiyacımız Var?
Pattern öğrenmenin üç faydası:
Ortak dil: "Bu bir Observer pattern" dediğinde, tüm geliştiriciler ne demek istediğini anlar
Kanıtlanmış çözüm: Tekerleği yeniden icat etme, deneyimden yararlan
Tasarım düşüncesi: Kodu nasıl yapılandıracağını düşünmeyi öğretir
Ama dikkat: pattern bilmek, her yere pattern koymak anlamına gelmez. Bunu dersin sonunda tekrar konuşacağız.
Singleton — Tek Instance
Problem
Bazı nesnelerden sadece bir tane olmasını istersin: veritabanı bağlantısı, konfigürasyon yöneticisi, logger...
Analoji
Bir ülkede tek bir merkez bankası vardır. İkinci bir tane açmazsın. Kim başvurursa başvursun, aynı merkez bankasına gider.
Python'da Modül = Singleton
Python'da en basit singleton, modülün kendisidir. Bir modül ilk import edildiğinde çalışır ve sonraki import'larda aynı nesne döner:
# config.py — Bu dosyanın kendisi bir singleton!
import json
_config = None
def _load_config():
global _config
if _config is None:
with open("config.json") as f:
_config = json.load(f)
return _config
def get(key, default=None):
config = _load_config()
return config.get(key, default)
# Kullanım (başka dosyada)
# import config
# db_host = config.get("database_host", "localhost")Her yerden import config yaptığında aynı nesneye erişirsin. Başka bir şeye gerek yok!
Sınıf ile Singleton
Eğer gerçekten bir sınıf istiyorsan:
class DatabaseConnection:
_instance = None
def __new__(cls, *args, **kwargs):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self, host="localhost", port=5432):
if self._initialized:
return
self.host = host
self.port = port
self._initialized = True
print(f"Bağlantı oluşturuldu: {host}:{port}")
# Test
db1 = DatabaseConnection("localhost", 5432)
db2 = DatabaseConnection("remote", 3306) # Bu çağrıda yeni instance oluşmaz
print(db1 is db2) # True — aynı nesne!
print(db2.host) # "localhost" — ilk init geçerli💡 İpucu: Python'da singleton'a gerçekten ihtiyacın olduğu durumlar azdır. Çoğu zaman bir modül-seviye değişken veya basit bir fonksiyon yeterlidir. Singleton'ı test etmek zordur çünkü global state oluşturur.
Factory — Nesne Oluşturmayı Soyutlama
Problem
Farklı koşullara göre farklı nesneler oluşturman gerekiyor. if/elif zinciri yazmak istemiyorsun.
Analoji
Bir pizza restoranına giriyorsun. "Margarita" diyorsun, mutfak nasıl yapacağını biliyor. "Karışık" diyorsun, farklı bir tarif uygulanıyor. Sen sadece istediğini söylüyorsun, yapım detayı mutfağın sorumluluğu.
Basit Factory
class Dog:
def speak(self):
return "Hav hav!"
class Cat:
def speak(self):
return "Miyav!"
class Bird:
def speak(self):
return "Cik cik!"
def create_animal(animal_type):
"""Factory fonksiyonu — nesne oluşturmayı soyutlar"""
animals = {
"dog": Dog,
"cat": Cat,
"bird": Bird,
}
animal_class = animals.get(animal_type.lower())
if animal_class is None:
raise ValueError(f"Bilinmeyen hayvan: {animal_type}")
return animal_class()
# Kullanım
animal = create_animal("dog")
print(animal.speak()) # Hav hav!
animal = create_animal("cat")
print(animal.speak()) # Miyav!Dictionary'ye sınıf referanslarını koymak, Python'ın first-class özelliği sayesinde çok doğal.
Parametreli Factory
class Notification:
def send(self, message):
raise NotImplementedError
class EmailNotification(Notification):
def __init__(self, recipient):
self.recipient = recipient
def send(self, message):
print(f"📧 Email to {self.recipient}: {message}")
class SMSNotification(Notification):
def __init__(self, phone):
self.phone = phone
def send(self, message):
print(f"📱 SMS to {self.phone}: {message}")
class PushNotification(Notification):
def __init__(self, device_id):
self.device_id = device_id
def send(self, message):
print(f"🔔 Push to {self.device_id}: {message}")
class NotificationFactory:
_creators = {
"email": EmailNotification,
"sms": SMSNotification,
"push": PushNotification,
}
@classmethod
def create(cls, channel, **kwargs):
creator = cls._creators.get(channel)
if not creator:
raise ValueError(f"Desteklenmeyen kanal: {channel}")
return creator(**kwargs)
@classmethod
def register(cls, channel, creator_class):
"""Yeni bildirim tipi ekle — genişlemeye açık!"""
cls._creators[channel] = creator_class
# Kullanım
notif = NotificationFactory.create("email", recipient="ali@example.com")
notif.send("Siparişiniz hazır!")
notif = NotificationFactory.create("sms", phone="+905551234567")
notif.send("Kodunuz: 1234")register metodu sayesinde, mevcut kodu değiştirmeden yeni bildirim türleri ekleyebilirsin (Open/Closed prensibi).
Observer — Olay Sistemi
Problem
Bir nesnede değişiklik olduğunda, diğer nesnelerin haberdar olmasını istiyorsun. Ama bu nesneler birbirini bilmemeli.
Analoji
YouTube aboneliği gibi. Bir kanala abone oluyorsun. Kanal yeni video yüklediğinde sana bildirim geliyor. Kanal senin kim olduğunu bilmek zorunda değil — sadece abonelerine bildiriyor.
Basit Observer (Callback Tabanlı)
class EventEmitter:
def __init__(self):
self._listeners = {}
def on(self, event, callback):
"""Olaya abone ol"""
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(callback)
def off(self, event, callback):
"""Aboneliği iptal et"""
if event in self._listeners:
self._listeners[event].remove(callback)
def emit(self, event, *args, **kwargs):
"""Olayı tetikle — tüm abonelere bildir"""
for callback in self._listeners.get(event, []):
callback(*args, **kwargs)
# Kullanım
emitter = EventEmitter()
# Abone ol
def on_user_login(user):
print(f"📊 Analytics: {user} giriş yaptı")
def on_user_login_log(user):
print(f"📝 Log: {user} giriş yaptı - {__import__('datetime').datetime.now()}")
def on_user_login_welcome(user):
print(f"👋 Welcome back, {user}!")
emitter.on("user_login", on_user_login)
emitter.on("user_login", on_user_login_log)
emitter.on("user_login", on_user_login_welcome)
# Olay gerçekleştiğinde
emitter.emit("user_login", "Ahmet")Çıktı:
📊 Analytics: Ahmet giriş yaptı
📝 Log: Ahmet giriş yaptı - 2024-01-15 10:30:00
👋 Welcome back, Ahmet!Üç farklı modül, birbirini bilmeden, aynı olaya tepki veriyor. Gevşek bağlılık (loose coupling) böyle sağlanır.
Event Bus — Uygulama Çapında Olay Sistemi
class EventBus:
"""Uygulama çapında tek bir event bus (singleton gibi)"""
_instance = None
def __new__(cls):
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance._listeners = {}
return cls._instance
def subscribe(self, event, callback):
if event not in self._listeners:
self._listeners[event] = []
self._listeners[event].append(callback)
return lambda: self._listeners[event].remove(callback)
def publish(self, event, data=None):
for callback in self._listeners.get(event, []):
try:
callback(data)
except Exception as e:
print(f"Event handler error: {e}")
# Kullanım
bus = EventBus()
# Modül A: Sipariş servisi
def handle_order_created(order):
print(f"📦 Stok güncelleniyor: {order['product']}")
# Modül B: Bildirim servisi
def notify_order_created(order):
print(f"📧 Müşteriye email gönderiliyor: {order['customer']}")
bus.subscribe("order_created", handle_order_created)
bus.subscribe("order_created", notify_order_created)
# Sipariş oluşturulduğunda
bus.publish("order_created", {
"product": "Laptop",
"customer": "ali@example.com",
"total": 15000
})Strategy — Algoritma Değiştirme
Problem
Aynı işi farklı şekillerde yapmak istiyorsun. Hangi yöntemi kullanacağın çalışma zamanında belli oluyor.
Analoji
Navigasyon uygulaması gibi. Hedefe arabayla mı, yürüyerek mi, toplu taşımayla mı gideceksin? Aynı hedef, farklı stratejiler. Stratejiyi değiştirdiğinde rotayı yeniden hesaplar.
Python'da Strategy = Fonksiyon Geçirmek
Python'da fonksiyonlar first-class citizendir. Bu yüzden Strategy pattern çok basit:
from typing import Callable
# Stratejiler (sadece fonksiyon)
def bubble_sort(data):
arr = data.copy()
n = len(arr)
for i in range(n):
for j in range(0, n - i - 1):
if arr[j] > arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
def quick_sort(data):
if len(data) <= 1:
return data
pivot = data[len(data) // 2]
left = [x for x in data if x < pivot]
middle = [x for x in data if x == pivot]
right = [x for x in data if x > pivot]
return quick_sort(left) + middle + quick_sort(right)
def python_sort(data):
return sorted(data)
# Context — stratejiyi kullanan sınıf
class Sorter:
def __init__(self, strategy: Callable = python_sort):
self.strategy = strategy
def sort(self, data):
print(f"Strateji: {self.strategy.__name__}")
return self.strategy(data)
# Kullanım
data = [64, 34, 25, 12, 22, 11, 90]
sorter = Sorter(bubble_sort)
print(sorter.sort(data))
sorter.strategy = quick_sort # Strateji değiştir
print(sorter.sort(data))Java'da Strategy için interface + abstract class + concrete class lazım. Python'da bir fonksiyon yeter!
Pratik Örnek: Plugin Sistemi
class TextProcessor:
def __init__(self):
self.plugins = []
def register_plugin(self, plugin_func):
"""Yeni plugin ekle"""
self.plugins.append(plugin_func)
return plugin_func # Decorator olarak da kullanılabilir
def process(self, text):
"""Tüm plugin'leri sırayla uygula"""
result = text
for plugin in self.plugins:
result = plugin(result)
return result
# Plugin'ler — basit fonksiyonlar
processor = TextProcessor()
@processor.register_plugin
def remove_extra_spaces(text):
return " ".join(text.split())
@processor.register_plugin
def capitalize_sentences(text):
sentences = text.split(". ")
return ". ".join(s.capitalize() for s in sentences)
@processor.register_plugin
def add_period(text):
if text and not text.endswith("."):
return text + "."
return text
# Kullanım
raw_text = " merhaba dünya. bugün hava güzel. python harika "
result = processor.process(raw_text)
print(result)
# "Merhaba dünya. Bugün hava güzel. Python harika."Her plugin basit bir fonksiyon. Yeni plugin eklemek = yeni fonksiyon yazmak. Mevcut koda dokunmaya gerek yok!
Decorator Pattern — Wrapper (Sarmalayıcı)
Problem
Bir nesnenin davranışını, nesneyi değiştirmeden genişletmek istiyorsun.
Analoji
Hediye paketi gibi. Bir kitabı alıyorsun, kurdeleli kağıda sarıyorsun, kartpostal ekliyorsun. Kitap aynı kitap ama "süslenmiş" versiyonu var.
Not: Bu, Python'ın @decorator syntax'ından farklı bir kavram (ama ilişkili). Burada OOP pattern'inden bahsediyoruz.
Python'da Decorator Pattern
class TextComponent:
"""Temel arayüz"""
def render(self):
raise NotImplementedError
class PlainText(TextComponent):
def __init__(self, text):
self.text = text
def render(self):
return self.text
class BoldDecorator(TextComponent):
def __init__(self, component):
self.component = component
def render(self):
return f"<b>{self.component.render()}</b>"
class ItalicDecorator(TextComponent):
def __init__(self, component):
self.component = component
def render(self):
return f"<i>{self.component.render()}</i>"
class ColorDecorator(TextComponent):
def __init__(self, component, color):
self.component = component
self.color = color
def render(self):
return f'<span style="color:{self.color}">{self.component.render()}</span>'
# Kullanım — decorator'ları zincirleme
text = PlainText("Merhaba Dünya")
bold_text = BoldDecorator(text)
colored_bold_text = ColorDecorator(bold_text, "red")
print(text.render())
# Merhaba Dünya
print(bold_text.render())
# <b>Merhaba Dünya</b>
print(colored_bold_text.render())
# <span style="color:red"><b>Merhaba Dünya</b></span>Python Fonksiyonu ile Daha Basit
def bold(func):
def wrapper(*args, **kwargs):
return f"<b>{func(*args, **kwargs)}</b>"
return wrapper
def italic(func):
def wrapper(*args, **kwargs):
return f"<i>{func(*args, **kwargs)}</i>"
return wrapper
@bold
@italic
def greet(name):
return f"Merhaba {name}"
print(greet("Dünya"))
# <b><i>Merhaba Dünya</i></b>Python'ın @decorator syntax'ı, Decorator pattern'inin çok daha zarif bir uygulaması.
Iterator — Python'da Built-in
Problem
Bir koleksiyonun elemanlarını sırayla dolaşmak istiyorsun, ama koleksiyonun iç yapısını bilmek zorunda değilsin.
Python'da Her Şey Iterator Olabilir
Python'da __iter__ ve __next__ metodlarını implement eden her şey iterable'dır:
class Countdown:
"""Geri sayım iterator'ı"""
def __init__(self, start):
self.start = start
def __iter__(self):
self.current = self.start
return self
def __next__(self):
if self.current <= 0:
raise StopIteration
value = self.current
self.current -= 1
return value
# for döngüsü ile kullanım
for num in Countdown(5):
print(num, end=" ")
# 5 4 3 2 1
# Manuel kullanım
counter = iter(Countdown(3))
print(next(counter)) # 3
print(next(counter)) # 2
print(next(counter)) # 1
# next(counter) # StopIteration!Generator ile Daha Kolay
Aslında Python'da iterator yazmak için genellikle sınıfa gerek yok — yield yeter:
def countdown(start):
"""Generator — otomatik iterator"""
current = start
while current > 0:
yield current
current -= 1
for num in countdown(5):
print(num, end=" ")
# 5 4 3 2 1
def fibonacci(limit):
"""Fibonacci sayıları"""
a, b = 0, 1
while a < limit:
yield a
a, b = b, a + b
print(list(fibonacci(100)))
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]Python'da Iterator pattern dile gömülü. Ayrı bir pattern olarak düşünmeye bile gerek yok — for döngüsü, list comprehension, map, filter hepsi iterator protocol kullanır.
Context Manager — Kaynak Yönetimi
Problem
Bir kaynak (dosya, veritabanı bağlantısı, lock) açıyorsun, kullanıyorsun, kapatman gerekiyor. Exception olsa bile kapatılmalı.
Analoji
Otel odası gibi. Check-in yaparsın, odayı kullanırsın, check-out yaparsın. Erken ayrılsan da, sorun çıksa da, oda temizlenmeli.
__enter__ ve __exit__ ile
class DatabaseConnection:
def __init__(self, host):
self.host = host
self.connected = False
def __enter__(self):
print(f"🔗 {self.host} bağlantısı açıldı")
self.connected = True
return self
def __exit__(self, exc_type, exc_val, exc_tb):
print(f"🔌 {self.host} bağlantısı kapatıldı")
self.connected = False
if exc_type:
print(f"⚠️ Hata oluştu: {exc_val}")
return False # Exception'ı yeniden fırlat
def query(self, sql):
if not self.connected:
raise RuntimeError("Bağlantı yok!")
print(f"📊 Sorgu: {sql}")
return [{"id": 1, "name": "test"}]
# with bloğu ile kullanım
with DatabaseConnection("localhost") as db:
result = db.query("SELECT * FROM users")
print(result)
# Blok bitince otomatik kapanır
# Exception olsa bile kapanır!
try:
with DatabaseConnection("localhost") as db:
db.query("SELECT * FROM users")
raise ValueError("Bir hata!")
except ValueError:
pass
# "🔌 localhost bağlantısı kapatıldı" yine yazdırılırcontextmanager Decorator ile Daha Kolay
from contextlib import contextmanager
import time
@contextmanager
def timer(label="Operation"):
"""İşlem süresini ölçen context manager"""
start = time.perf_counter()
print(f"⏱️ {label} başladı...")
yield # Blok burada çalışır
elapsed = time.perf_counter() - start
print(f"⏱️ {label} tamamlandı: {elapsed:.4f}s")
# Kullanım
with timer("Veri işleme"):
data = [i ** 2 for i in range(1_000_000)]
total = sum(data)
# ⏱️ Veri işleme başladı...
# ⏱️ Veri işleme tamamlandı: 0.1234s
@contextmanager
def temporary_directory():
"""Geçici dizin oluştur, işlem bitince sil"""
import tempfile
import shutil
dirpath = tempfile.mkdtemp()
print(f"📁 Geçici dizin: {dirpath}")
try:
yield dirpath
finally:
shutil.rmtree(dirpath)
print(f"🗑️ Geçici dizin silindi")
with temporary_directory() as tmpdir:
# tmpdir ile çalış, blok bitince otomatik silinir
passPython'da Pattern'ler Neden Daha Basit?
Java veya C++ ile karşılaştırıldığında, Python'da birçok pattern çok daha basit implement edilir. Nedeni:
1. First-Class Functions
Fonksiyonları değişken gibi geçirebilirsin. Strategy pattern'i bir fonksiyon parametresi ile çözülür.
2. Duck Typing
"Eğer ördek gibi yürüyorsa ve ördek gibi vaklıyorsa, o bir ördektir." Interface tanımlamana gerek yok.
# Java'da: interface gerekli
# Python'da: uygun metodları implement et, yeter
class FileLogger:
def log(self, msg):
print(f"[FILE] {msg}")
class ConsoleLogger:
def log(self, msg):
print(f"[CONSOLE] {msg}")
def process(data, logger): # Herhangi bir logger olabilir
logger.log(f"Processing {data}")
process("test", FileLogger())
process("test", ConsoleLogger())3. Decorator Syntax
@decorator ile Decorator, Observer, ve benzeri pattern'ler çok zarif uygulanır.
4. Generator ve Context Manager
Iterator ve RAII (Resource Acquisition Is Initialization) pattern'leri dile gömülü.
5. Modüller = Singleton
Bir Python modülü doğal bir singleton'dır.
# Java'da Singleton: 20+ satır kod
# Python'da Singleton: config.py dosyası
# Java'da Strategy: interface + abstract class + concrete classes
# Python'da Strategy: fonksiyon parametresi
# Java'da Iterator: Iterator interface implement et
# Python'da Iterator: yield⚠️ Over-Engineering Uyarısı
Design pattern bilmek güzel ama her yere pattern koymaya çalışmak, en tehlikeli anti-pattern'dir: Over-Engineering.
# ❌ Over-engineered: 2 sayıyı toplamak için Strategy + Factory + Observer
class AdditionStrategy:
pass
class NumberFactory:
pass
class ResultObserver:
pass
# ... 100 satır kod ...
# ✅ Gerçeklik
def add(a, b):
return a + bNe Zaman Pattern Kullanmalısın?
Problem tekrar ediyorsa: Aynı yapısal sorunu 3. kez çözüyorsan, pattern düşün
Değişim bekliyorsan: "Bu kısmı ileride değiştirmem gerekecek" → Strategy
Gevşek bağlılık istiyorsan: Modüller birbirini bilmemeli → Observer
Doğal geliyorsa: Kodu okurken "bu bir Factory" diyorsan, zaten doğal yoldan pattern uygulamışsın
Ne Zaman Pattern Kullanmamalısın?
Basit sorun için: 10 satır kod yetiyorsa, pattern ekleme
Gösteriş için: "Design pattern biliyorum" demek için değil
İlk seferde: İlk versiyonu basit yaz, gerekirse refactor et
Anlam katmıyorsa: Pattern kodu daha karmaşık yapıyorsa, bir yanlışlık var
"Make it work, make it right, make it fast." — Kent Beck. Önce çalışsın, sonra güzelleştir.
Pratik: Event Bus ve Plugin Sistemi
Öğrendiklerimizi birleştirelim. Observer (Event Bus) ve Strategy (Plugin) pattern'lerini kullanan bir mini framework yazalım:
from typing import Callable, Any
from dataclasses import dataclass, field
from datetime import datetime
# === Event Bus (Observer Pattern) ===
class EventBus:
def __init__(self):
self._handlers: dict[str, list[Callable]] = {}
def on(self, event: str, handler: Callable):
self._handlers.setdefault(event, []).append(handler)
def emit(self, event: str, data: Any = None):
for handler in self._handlers.get(event, []):
handler(data)
# === Plugin System (Strategy Pattern) ===
class PluginManager:
def __init__(self):
self._plugins: dict[str, Callable] = {}
def register(self, name: str, plugin: Callable):
self._plugins[name] = plugin
def execute(self, name: str, *args, **kwargs):
if name not in self._plugins:
raise ValueError(f"Plugin bulunamadı: {name}")
return self._plugins[name](*args, **kwargs)
def list_plugins(self):
return list(self._plugins.keys())
# === Uygulama ===
@dataclass
class Task:
title: str
created_at: str = field(default_factory=lambda: datetime.now().isoformat())
completed: bool = False
class TaskApp:
def __init__(self):
self.tasks: list[Task] = []
self.events = EventBus()
self.plugins = PluginManager()
def add_task(self, title: str):
task = Task(title=title)
self.tasks.append(task)
self.events.emit("task_added", task)
return task
def complete_task(self, index: int):
task = self.tasks[index]
task.completed = True
self.events.emit("task_completed", task)
def export(self, format_name: str):
return self.plugins.execute(format_name, self.tasks)
# === Kurulum ===
app = TaskApp()
# Event listener'ları kaydet (Observer)
app.events.on("task_added", lambda t: print(f"✅ Eklendi: {t.title}"))
app.events.on("task_completed", lambda t: print(f"🎉 Tamamlandı: {t.title}"))
# Export plugin'lerini kaydet (Strategy)
app.plugins.register("json", lambda tasks: [
{"title": t.title, "done": t.completed} for t in tasks
])
app.plugins.register("csv", lambda tasks: "\n".join(
f"{t.title},{'done' if t.completed else 'pending'}" for t in tasks
))
# === Kullanım ===
app.add_task("Python öğren")
app.add_task("Test yaz")
app.complete_task(0)
print("\n--- JSON Export ---")
print(app.export("json"))
print("\n--- CSV Export ---")
print(app.export("csv"))Çıktı:
✅ Eklendi: Python öğren
✅ Eklendi: Test yaz
🎉 Tamamlandı: Python öğren
--- JSON Export ---
[{'title': 'Python öğren', 'done': True}, {'title': 'Test yaz', 'done': False}]
--- CSV Export ---
Python öğren,done
Test yaz,pendingÖzet
Design pattern'ler tekrar eden sorunlara kanıtlanmış çözümlerdir — ortak bir dil sağlar
Singleton: Tek instance — Python'da modül zaten singleton'dır
Factory: Nesne oluşturmayı soyutlar — dictionary + class referans ile basitçe yapılır
Observer: Olay sistemi — callback fonksiyonlarla loose coupling sağlar
Strategy: Algoritma değiştirme — Python'da fonksiyon geçirmekle çözülür, sınıfa gerek yok
Python'da pattern'ler daha basittir çünkü first-class functions, duck typing ve generator gibi dil özellikleri birçok pattern'i gereksiz kılar
Over-engineering'den kaçın: Pattern'i sorun gerektirdiğinde kullan, gösteriş için değil
AI Asistan
Sorularını yanıtlamaya hazır