Nesne Tabanlı Programlama 2

5 - Kompozisyon ve Polimorfizm

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2026

Bu Konuyu Neden Görüyoruz?

Sınıflar arasında ilişki kurarken en sık yapılan hatalardan biri, her benzerliği kalıtım sanmaktır.

Oysa iki temel ilişki vardır:

  • biri diğerinin bir türü olabilir,
  • biri diğerinin bir parçası olabilir.

Bu ayrımı doğru yapmak, daha düzenli ve daha anlaşılır kod yazmamızı sağlar.

İki Temel İlişki

Sınıflar arasında ilişki kurarken önce şu ayrımı yaparız:

  • is-a → bir tür ilişkisi
  • has-a → parça / sahip olma ilişkisi

Örnek:

  • Araba bir Tasit türüdür → is-a
  • Siparis, SiparisKalemi nesnelerine sahiptir → has-a

Önce bu ayrımı doğru yapmak gerekir.

Bu Derste Ne Öğreneceğiz?

Bu dersin sonunda şunları ayırt edebiliyor olmanız beklenir:

  • kalıtımın hangi durumda uygun olduğunu,
  • kompozisyonun hangi durumda daha doğru tercih olduğunu,
  • is-a ve has-a ilişkileri arasındaki farkı,
  • farklı nesnelerin aynı metot çağrısına farklı cevap verebildiğini,
  • kompozisyon ve polimorfizmin birlikte nasıl kullanılabildiğini.

Kalıtım Nedir?

Kalıtım, bir sınıfın başka bir sınıftan özellik ve davranış devralmasıdır.

Burada temel düşünce şudur:

Alt sınıf, üst sınıfın bir türüdür.

Örnekler:

  • Araba bir Tasit türüdür
  • Kedi bir Hayvan türüdür

Bu nedenle kalıtımda sormamız gereken soru şudur:

“Bu sınıf gerçekten ötekinin bir türü mü?”

Kompozisyon Nedir?

Kompozisyon, bir nesnenin görevini yerine getirmek için başka nesneleri yapısının parçası olarak kullanmasıdır.

Burada temel düşünce şudur:

Bir nesne, başka nesnelerden oluşabilir.

Örnek:

  • Siparis, birden fazla SiparisKalemi içerir
  • Kitap, birden fazla Bolum içerir

Burada tür ilişkisi değil, parça-bütün ilişkisi vardır.

Hangisini Seçmeliyim?

Bir ilişki kurarken şu iki soruyu sorun:

  1. Bu gerçekten bir tür ilişkisi mi?
    • Evetse kalıtım düşünülebilir.
  2. Bu yapı başka parçalardan mı oluşuyor?
    • Evetse kompozisyon daha uygundur.

Pratik kural:

Sırf kod tekrarını azaltmak için hemen kalıtıma gitmeyin. Önce ilişkinin türünü düşünün.

Somut Örnek: Sipariş ve Sipariş Kalemi

Aşağıdaki ilişkiye bakalım:

  • bir siparişin içinde birden fazla kalem olabilir,
  • her kalem kendi ürününü, fiyatını ve adedini bilir,
  • sipariş ise tüm kalemleri bir arada yönetir.

Burada doğru model şudur:

  • Siparis, SiparisKalemi değildir
  • Siparis, SiparisKalemi nesnelerine sahiptir

Yani burada uygun yaklaşım kompozisyondur.

Kompozisyon Örneği: Sipariş ve Sipariş Kalemi

class SiparisKalemi:
    def __init__(self, urun_adi, birim_fiyat, adet):
        self.urun_adi = urun_adi
        self.birim_fiyat = birim_fiyat
        self.adet = adet

    def tutar(self):
        return self.birim_fiyat * self.adet


class Siparis:
    def __init__(self, siparis_no):
        self.siparis_no = siparis_no
        self.kalemler = []

    def kalem_ekle(self, kalem):
        self.kalemler.append(kalem)

    def toplam_tutar(self):
        return sum(kalem.tutar() for kalem in self.kalemler)

Kullanım

siparis = Siparis("SP-1001")
siparis.kalem_ekle(SiparisKalemi("Klavye", 750, 1))
siparis.kalem_ekle(SiparisKalemi("Mouse", 350, 2))

print(siparis.toplam_tutar())
# Çıktı:
# 1450

Burada:

  • Siparis, SiparisKalemi nesnelerine sahiptir
  • toplam hesabı Siparis içinde yapılır
  • her kalem kendi tutarını kendisi bilir

Neden Daha Doğru Bir Tasarım?

Çünkü sorumluluklar ayrılmıştır:

  • ürünün miktar / fiyat hesabı → SiparisKalemi
  • siparişin toplamı → Siparis

Eğer tüm mantık tek sınıfta toplansaydı:

  • sınıf gereksiz yere büyürdü,
  • okunması zorlaşırdı,
  • değişiklik yapmak daha riskli olurdu.

Kompozisyonun Avantajları

  • Görev paylaşımı: Her nesne kendi işini yapar.
  • Bakım kolaylığı: Bir parçayı değiştirirken tüm yapıyı bozma ihtimali azalır.
  • Yeniden kullanım: Alt nesneler başka yerlerde de kullanılabilir.
  • Esneklik: Ana yapı, farklı parçalardan oluşacak şekilde genişletilebilir.

Peki Polimorfizm Nedir?

İkinci büyük kavramımız polimorfizmdir.

En sade haliyle:

Aynı metot çağrısı, farklı nesnelerde farklı sonuç üretebilir.

Burada önemli olan şudur:

  • istemci kod, nesnenin iç ayrıntısını bilmek zorunda değildir,
  • sadece beklenen davranışın var olması yeterlidir.

Kısaca:

Aynı mesaj, farklı cevap.

Polimorfizme Neden İhtiyaç Duyarız?

Aşağıdaki yaklaşım ilk bakışta çalışır; ama büyümeye uygun değildir:

def odeme_al(tur, tutar):
    if tur == "kart":
        return f"{tutar} TL kredi kartı ile ödendi"
    elif tur == "havale":
        return f"{tutar} TL havale ile ödendi"
    elif tur == "cuzdan":
        return f"{tutar} TL dijital cüzdan ile ödendi"

Sorunlar:

  • her yeni ödeme türünde fonksiyon değişecek,
  • if/elif zinciri büyüyecek,
  • davranışlar tek yerde toplanacak.

Daha İyi Yaklaşım: Ortak Bir Sözleşme

class OdemeYontemi:
    def ode(self, tutar):
        raise NotImplementedError(
            "Bu metot alt sınıfta tanımlanmalıdır"
        )


class KrediKarti(OdemeYontemi):
    def ode(self, tutar):
        return f"{tutar} TL kredi kartı ile ödendi"


class Havale(OdemeYontemi):
    def ode(self, tutar):
        return f"{tutar} TL havale ile ödendi"


class DijitalCuzdan(OdemeYontemi):
    def ode(self, tutar):
        return f"{tutar} TL dijital cüzdan ile ödendi"

Burada ortak beklenti açıktır:

Her ödeme yöntemi ode() metodunu sağlamalıdır.

Kullanım: Polimorfizm İş Başında

yontemler = [KrediKarti(), Havale(), DijitalCuzdan()]

for yontem in yontemler:
    print(yontem.ode(250))

# Çıktı:
# 250 TL kredi kartı ile ödendi
# 250 TL havale ile ödendi
# 250 TL dijital cüzdan ile ödendi

Buradaki Temel Fikir

Döngü şunu sormuyor:

  • “Bu nesne KrediKarti mı?”
  • “Bu nesne Havale mi?”

Döngü sadece şunu varsayıyor:

ode() metodunu çağırabilirim.

Polimorfizmin gücü buradadır:

  • tür kontrolü azalır,
  • istemci kod daha sade olur,
  • yeni sınıf eklemek kolaylaşır.

Polimorfizmin Sağladıkları

  • Esneklik: Yeni sınıflar eklemek kolaylaşır.
  • Daha sade istemci kodu: Tür kontrolü azalır.
  • Genişletilebilirlik: Eski çalışan kodu değiştirme ihtiyacı azalır.
  • Bakım kolaylığı: Davranışlar tek fonksiyonda toplanmaz.

Duck Typing Nedir?

Python’da bazen ortak davranış için ortak bir üst sınıf yazmak zorunda olmayız.

Bu yaklaşımın yaygın adı duck typing’dir.

Adı şu düşünceden gelir:

Eğer bir şey ördek gibi yürüyorsa ve ördek gibi vaklıyorsa, ona ördek gibi davranırım.

Buradaki temel fikir şudur:

  • nesnenin türünden çok,
  • gerekli davranışı sağlayıp sağlamadığı önemlidir.

Yani ortak üst sınıf olmasa da, gerekli metot varsa nesne kullanılabilir.

Duck Typing Örneği: Rapor Üreticileri

class PDFRapor:
    def olustur(self):
        return "PDF raporu oluşturuldu"


class CSVRapor:
    def olustur(self):
        return "CSV raporu oluşturuldu"


class HTMLRapor:
    def olustur(self):
        return "HTML raporu oluşturuldu"


def rapor_uret(uretici):
    print(uretici.olustur())

Kullanım

rapor_uret(PDFRapor())
rapor_uret(CSVRapor())
rapor_uret(HTMLRapor())

# Çıktı:
# PDF raporu oluşturuldu
# CSV raporu oluşturuldu
# HTML raporu oluşturuldu

Burada kalıtım yoktur; ama ortak davranış vardır:

olustur()

Duck Typing’de Dikkat Edilmesi Gereken Nokta

Duck typing, yalnızca aynı isimli metot olması demek değildir.

Şunlar da uyumlu olmalıdır:

  • metot ne bekliyor,
  • nasıl çağrılıyor,
  • ne döndürüyor.

Örneğin:

class BozukRapor:
    def olustur(self, dosya_yolu):
        return "rapor"

Eğer rapor_uret() fonksiyonu parametresiz olustur() bekliyorsa, burada sorun çıkar.

Yani yalnızca metot adı değil, kullanım biçimi de önemlidir.

Kompozisyon + Polimorfizm Birlikte

Gerçek projelerde bu iki kavram çoğu zaman birlikte kullanılır.

Örnek fikir:

  • Sepet sınıfı bir ödeme yöntemi nesnesine sahiptir → kompozisyon
  • hangi ödeme yöntemi verilirse verilsin ode() çağrılır → polimorfizm

Birlikte Örnek: Sepet ve Ödeme Yöntemi

class Sepet:
    def __init__(self, odeme_yontemi):
        self.odeme_yontemi = odeme_yontemi

    def satin_al(self, tutar):
        print(self.odeme_yontemi.ode(tutar))


sepet1 = Sepet(KrediKarti())
sepet2 = Sepet(Havale())

sepet1.satin_al(500)
sepet2.satin_al(500)

# Çıktı:
# 500 TL kredi kartı ile ödendi
# 500 TL havale ile ödendi

Bu Tasarım Neden Güçlü?

Çünkü Sepet şunları bilmek zorunda değildir:

  • ödeme kartla mı yapılıyor,
  • havale ile mi yapılıyor,
  • yarın yeni bir yöntem eklenecek mi.

Sepet için önemli olan tek şey şudur:

Elimde ode() metodu olan bir nesne var mı?

Bu yaklaşım sınıflar arasındaki bağımlılığı azaltır.

Sık Yapılan Hatalar

  1. has-a ilişkisinde yanlışlıkla kalıtım kullanmak
  2. Kalıtımı sadece kod tekrarını azaltmak için kurmak
  3. Taban sınıfta beklentiyi belirsiz bırakmak
  4. Duck typing’de beklenen davranışı net düşünmemek
  5. Her ilişkiyi tek bir kalıpla açıklamaya çalışmak

Özet

  • Kompozisyon, bir nesnenin başka nesneleri yapısının parçası olarak kullanmasıdır.
  • Polimorfizm, aynı beklentiyle farklı nesnelerin kullanılabilmesidir.
  • Duck typing, Python’da ortak davranışın kalıtım olmadan da kurulabilmesidir.

Bu derste akılda tutulması gereken temel soru şudur:

Bu ilişki is-a mı, yoksa has-a mı?

İyi tasarımda amaç, sınıfları gereğinden fazla birbirine bağlamadan birlikte çalıştırabilmektir.

Alıştırmalar

Alıştırma 1: Kompozisyon - Kitap ve Bölümler

  • Bir Bolum sınıfı ve bir Kitap sınıfı tasarlayın.
  • Her kitap, birden fazla bölüm nesnesi içersin.
  • Bolum sınıfında bölüm adı ve sayfa sayısı olsun.
  • Kitap sınıfında kitabın adını, bölümlerini ve toplam sayfa sayısını gösteren bir metot yazın.

Beklenen Kullanım

kitap = Kitap("Python ile Programlama")
kitap.bolum_ekle(Bolum("Giriş", 12))
kitap.bolum_ekle(Bolum("Fonksiyonlar", 25))

kitap.bilgi_ver()
# Çıktı:
# Kitap: Python ile Programlama
# Bölüm sayısı: 2
# Toplam sayfa: 37

Alıştırma 2: Polimorfizm - Taşıtlar

  • Tasit adında bir temel sınıf oluşturun.
  • Bu sınıftan türeyen Araba, Bisiklet ve Ucak sınıflarını yazın.
  • Her sınıfta hareket_et() metodu farklı çıktı üretsin.
  • Ardından bir liste içinde bu nesneleri gezip aynı metodu çağırın.

Beklenen Kullanım

tasitlar = [Araba(), Bisiklet(), Ucak()]

for tasit in tasitlar:
    print(tasit.hareket_et())

# Çıktı:
# Araba yolda gidiyor
# Bisiklet pedal çeviriyor
# Uçak havada uçuyor

Alıştırma 3: Duck Typing - Rapor Üreticileri

Bu alıştırmayı dersten sonra kısa pratik olarak çözün.

  • PDFRapor ve HTMLRapor adında iki sınıf oluşturun.
  • Her iki sınıfta da olustur() adında bir metot olsun.
  • Aynı adlı bir fonksiyon, aldığı nesnenin olustur() metodunu çağırsın.
  • Bu fonksiyonu iki farklı nesne ile test edin.

Beklenen Kullanım

def rapor_uret(uretici):
    print(uretici.olustur())


rapor_uret(PDFRapor())
rapor_uret(HTMLRapor())

# Çıktı:
# PDF raporu oluşturuldu
# HTML raporu oluşturuldu

Alıştırma 4: Kompozisyon + Polimorfizm - Oyun ve Karakterler

Bu alıştırmayı dersten sonra kısa pratik olarak çözün.

  • Karakter adında bir temel sınıf oluşturun.
  • Savasci, Buyucu ve Okcu sınıfları bu sınıftan türesin.
  • Her sınıfta saldir() metodu farklı bir çıktı versin.
  • Oyun sınıfı, karakter nesnelerini bir listede tutsun.
  • Oyun sınıfı, listedeki tüm karakterlerin saldırmasını sağlasın.

Beklenen Kullanım

oyun = Oyun()
oyun.karakter_ekle(Savasci())
oyun.karakter_ekle(Buyucu())
oyun.karakter_ekle(Okcu())

oyun.tum_karakterler_saldir()

# Çıktı:
# Savaşçı kılıçla saldırıyor
# Büyücü ateş topu atıyor
# Okçu ok atıyor

Alıştırma 5: Tasarım Kararı Verme

Aşağıdaki yapıyı inceleyin:

  • Universite
  • Fakulte
  • Bolum
  • OgretimElemani
  • Ogrenci

Bu kavramlar arasındaki ilişkilerden hangileri:

  • kalıtıma,
  • kompozisyona

daha yakın görünüyor?

Her kararınızı kısa bir gerekçe ile açıklayın.

İpucu

Sadece “birlikte bulunuyorlar mı?” diye düşünmeyin.

Şu iki soruyu ayrı ayrı sorun:

  • “Bu bir tür ilişkisi mi?”
  • “Bu bir parça ilişkisi mi?”