Kompozisyon, delegation ve nesne koleksiyonları

Gerçek uygulamalar tek bir sınıftan oluşmaz. Bir sipariş müşteriye ve ürünlere, bir kütüphane kitaplara ve üyelere, bir takım oyunculara sahiptir. Bu hafta nesneler arasındaki has-a ilişkisini, kompozisyonu ve bir nesnenin başka nesnelere işi devretmesini (delegation) kullanacağız.

Bu bölümün kapsamı

NoteSınavda sorulur
  • nesnenin başka nesneyi tuttuğu has-a ilişkisi
  • koleksiyonun nesneleri değil referanslarını tutması
  • iç mutable koleksiyonu dışarı açmanın kapsüllemeyi delmesi
  • okunabilir görünüm için demet veya kopya döndürme
  • davranışı kuralın sahibine bırakan delegasyon
  • sorumluluğu yanlış sınıfa yığmanın sonucu
  • bir-çok ilişkide liste ile sözlük seçimi
  • UML’deki aggregation / composition ayrımının formal tanımı
  • ORM ve kalıcı veri katmanı
  • collections.abc koleksiyon protokolleri

Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.

Bu davranış hangi nesnenin işi?

Bir sipariş, ürünlerinin fiyat kuralını kendisi yazmalı mı, yoksa ürüne mi sormalı? Kütüphane kitabın durumunu doğrudan mı değiştirmeli? İki soruda da cevap kodun çalışıp çalışmadığına bakarak bulunmaz; kuralın kime ait olduğuna bakarak bulunur.

Bir nesne başka nesneyi tutabilir

Order, bir Customer nesnesine sahiptir: Order has a Customer.

Bir nesnenin başka bir nesneyi içererek has-a ilişkisi kurmasını gösteren kompozisyon diyagramı.

Kompozisyon ve has-a ilişkisi
NoteKompozisyon ve agregasyon terimleri

Bu derste kompozisyon sözcüğünü, nesneleri içererek/kullanarak davranış oluşturma biçimindeki geniş has-a yaklaşımı için kullanıyoruz. UML ve bazı kaynaklarda aggregation (agregasyon) ile composition (bileşim), parçanın yaşam döngüsünün bütüne ne ölçüde bağlı olduğuna göre ayrıca ayrılır. Bu ayrımı tanımanız yararlıdır; ancak bu hafta uygulama ve ölçme beklentimiz formal UML ayrımı değil, doğru nesne ilişkisi ve sorumluluk tasarımıdır.

Nesne koleksiyonu

_items, sayı listesi değil Product nesnelerine ait referansları tutar.

Kritik sentez: iç koleksiyonu dışarı açmak kapsüllemeyi delebilir

Şu tasarım ilk bakışta kullanışlı görünür:

class Order:
    def __init__(self):
        self.items = []

    def add_product(self, product):
        # burada tekrar, stok veya başka kurallar olabilir
        self.items.append(product)

Ama çağıran kod şunu yapabilir:

order.items.append(product)

Böylece add_product() içindeki bütün kontroller atlanır. Daha da önemlisi, items listesini doğrudan döndüren bir property de aynı liste nesnesine referans verirse sorun çözülmüş olmaz:

@property
def items(self):
    return self._items  # dış kod yine append yapabilir

Dışarıya yalnızca okunabilir bir görünüm vermek istiyorsak örneğin:

@property
def items(self):
    return tuple(self._items)

veya bağımsız sığ kopya gerekiyorsa:

return self._items.copy()

kullanılabilir. Hangi yaklaşımın uygun olduğu arayüz sözleşmesine bağlıdır.

Important3. hafta + 8. hafta burada birleşiyor

Bu sorun iki önceki fikrin doğrudan birleşimidir: 3. haftada mutable nesnelerde aliasing gördük; 8. haftada durum değişimini kontrollü bir arayüzden geçirmek gerektiğini öğrendik. İç listeyi dışarı verdiğiniz anda dış kod aynı mutable nesneye referans alır ve sınıfın kontrol kapısını dolaşabilir.

Delegation: işi sahibine bırakmak

OrderLine, ürünün fiyatlandırma kuralını kopyalamaz; Product.price_for() davranışına delegasyon yapar.

ImportantDelegation mekanik bir kural değildir

Amaç “her çağrıyı başka nesneye devretmek” değil, davranışı kuralın gerçek sahibine yerleştirmektir. Basit ara toplamın OrderLine içinde kalması da bağlama göre makul olabilir.

Sorumluluk yanlış yerdeyse ne olur?

class Library:
    def borrow_book(self, member, book):
        book.is_borrowed = True
        member.borrowed_count += 1
        # onlarca kural...

Library her nesnenin iç durumunu doğrudan yönetirse hızla “her şeyi yapan” merkeze dönüşür. Book.borrow() ve Member.add_borrowed_book() gibi davranışlar kendi nesnelerine ait olabilir; Library işlemi koordine eder.

Bir koleksiyonun nesnelerin kendilerini değil nesne referanslarını tuttuğunu gösteren diyagram.

Nesne koleksiyonu

Çokluk ilişkisi ve koleksiyon seçimi

  • Tek nesne: order.customer
  • Çok sayıda nesne: order.lines

Bir-çok ilişkide liste veya sözlük seçimi kullanım biçimine göre yapılmalıdır. Üye numarasından doğrudan erişim gerekiyorsa sözlük doğal olabilir:

Tekrar kontrolü koleksiyonu yöneten sınıfın sorumluluğundadır.

Alıştırma — İlişkiyi belirle

Car–Engine, Order–Customer, Playlist–Song, Student–Course çiftlerinde ilişkinin has-a olup olmadığını ve hangi tarafın diğerini tutmasının makul olduğunu tartışın. Her ilişkiyi iki tarafta da saklamanın senkronizasyon maliyetini ayrıca düşünün.

Alıştırma — Nesne koleksiyonunu tamamla

self._players.append(player)

Küçük üretim görevi — Kütüphane modeli

Book, Member, Library sınıflarıyla küçük bir model kurun:

  • Kitap ISBN ile tanımlansın,
  • Aynı ISBN iki kez eklenemesin,
  • Üye numarası benzersiz olsun,
  • Library kitap ve üyeleri koleksiyonlarda tutsun,
  • Koleksiyonların mutable iç nesnelerini doğrudan dışarı vermesin,
  • Ödünç alma davranışının hangi sınıfa/sınıflara ait olduğuna gerekçeyle karar verin.

Çalışır kod teslimi: İki kitap ve iki üye ekleyin; tekrarlı ISBN ve üye numarası durumlarının hata verdiğini assert/try-except ile doğrulayın. Ayrıca dışarıya verdiğiniz koleksiyon görünümünün append() ile değiştirilemediğini veya değişikliğin iç koleksiyonu etkilemediğini gösterin.

Sınav provası

Önce kendi cevabını seç; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu oku. Çeldiriciler uydurma değil, bu konuda gerçekten yapılan hatalardır. Sınav maddeleri de bu mantıkla yazılır.

Madde 1. Aşağıdaki program ne yazdırır?

class Customer:
    def __init__(self, name):
        self.name = name

class Order:
    def __init__(self, customer):
        self.customer = customer

customer = Customer("Ada")
order = Order(customer)
customer.name = "Ece"

print(order.customer.name)
  • A. Customer
  • B. Ada; nesne __init__ sırasında kopyalanır
  • C. AttributeError; order.customer.name iki adımlı erişim geçersizdir
  • D. None
  • E. Ece; order.customer aynı Customer nesnesini gösterir

Madde 2. Aşağıdaki program ne yazdırır?

class Product:
    def __init__(self, name, price):
        self.name = name
        self.price = price

class Order:
    def __init__(self):
        self._items = []

    def add_product(self, product):
        self._items.append(product)

    def total(self):
        return sum(product.price for product in self._items)

order = Order()
order.add_product(Product("Klavye", 1000))
order.add_product(Product("Mouse", 500))
print(order.total())
  • A. 1500
  • B. 2
  • C. TypeError; nesneler doğrudan toplanamaz
  • D. None
  • E. 0

Madde 3. Aşağıdaki tasarımda order.items.append(product) çağrısı neden sorunludur?

class Order:
    def __init__(self):
        self.items = []

    def add_product(self, product):
        # tekrar, stok veya başka kurallar
        self.items.append(product)
  • A. Sorun yoktur; add_product() yine de çağrılır
  • B. Dış kod iç listeye doğrudan eriştiği için add_product() içindeki bütün kontroller atlanır
  • C. append bir listeye nesne ekleyemez
  • D. Liste sıralamasını bozduğu için
  • E. items bir property olmadığı için AttributeError verir

Madde 4. Aşağıdaki property iç listeyi gerçekten korur mu?

@property
def items(self):
    return self._items
  • A. Evet; Python property dönüşlerini kendiliğinden kopyalar
  • B. Evet; property ile döndürülen değerler salt okunur olur
  • C. Hayır; aynı liste nesnesine referans verdiği için dış kod yine append yapabilir
  • D. Evet; _balance gibi tek alt çizgili adlar dışarıdan değiştirilemez
  • E. Hayır; ama @items.setter eklenirse sorun çözülür

Madde 5. Dışarıya yalnızca okunabilir bir görünüm vermek isteyen property hangisidir?

  • A. return len(self._items)
  • B. return list(self._items) (kopya)
  • C. return self._items
  • D. return tuple(self._items)
  • E. return self._items.clear()

Madde 6. Aşağıdaki tasarımda OrderLine.subtotal() neden fiyatlandırma kuralını kendisi yazmaz?

class OrderLine:
    def __init__(self, product, quantity):
        self.product = product
        self.quantity = quantity

    def subtotal(self):
        return self.product.price_for(self.quantity)
  • A. OrderLine sınıfı Product sınıfından türetildiği için
  • B. Python bir metot içinde çarpma işlemine izin vermediği için
  • C. Hesap OrderLine içinde yazılırsa AttributeError oluşacağı için
  • D. Delegasyon her çağrıda zorunlu olduğu için
  • E. Kural ürünün sorumluluğundadır; OrderLine davranışı sahibine devreder ve kural değişince tek yer güncellenir

Madde 7. Aşağıdaki tasarımın uzun vadede sorunu nedir?

class Library:
    def borrow_book(self, member, book):
        book.is_borrowed = True
        member.borrowed_count += 1
        # onlarca kural...
  • A. Library her nesnenin iç durumunu doğrudan yönettiği için “her şeyi yapan” bir merkeze dönüşür
  • B. Sorun yoktur; koordinasyon sınıfı bütün kuralları taşımalıdır
  • C. borrowed_count bir sınıf niteliği olmalıdır
  • D. Bir metot iki farklı nesneyi parametre olarak alamaz
  • E. is_borrowed niteliği dışarıdan değiştirilemez

Madde 8. Yukarıdaki Library tasarımını düzeltmek için en uygun adım nedir?

  • A. Book ve Member sınıflarını kaldırıp her şeyi sözlükte tutmak
  • B. Book.borrow() ve Member.add_borrowed_book() davranışlarını tanımlayıp Librarynin yalnız işlemi koordine etmesini sağlamak
  • C. borrow_book metodunu modül düzeyinde bir fonksiyona taşımak
  • D. Bütün nitelikleri property yapmak
  • E. Library sınıfını Book ve Member sınıflarından türetmek

Madde 9. Üye numarasından doğrudan erişim gerekiyorsa Library._members hangi yapıda tutulmalıdır?

  • A. Demet; üye listesi değişmemelidir
  • B. Liste; sıra korunduğu için arama daha kolaydır
  • C. Sözlük; anahtar üye numarası, değer Member nesnesi olur ve arama listeyi taramadan yapılır
  • D. Küme; üyeler benzersiz olduğu için
  • E. Her üye için ayrı bir nitelik

Madde 10. Aşağıdaki add_member metodunda tekrar kontrolünün burada yapılması neyi gösterir?

def add_member(self, member):
    if member.member_id in self._members:
        raise ValueError("Aynı üye numarası zaten var")
    self._members[member.member_id] = member
  • A. Üye numarasının sınıf niteliği olması gerektiğini
  • B. raise yerine assert kullanılması gerektiğini
  • C. Member sınıfının __eq__ metodunun eksik olduğunu
  • D. Koleksiyonun bütünlüğünü korumak, koleksiyonu yöneten sınıfın sorumluluğudur
  • E. Sözlüğün aynı anahtarı iki kez kabul ettiğini

Madde 1 — Doğru: E. Kompozisyonda nesne kopyalanmaz; referans tutulur. Müşterinin durumu değişince sipariş üzerinden de yeni değer görülür.

  • A yanlış: Yazdırılan, sınıf adı değil nitelik değeridir.
  • B yanlış: Python argümanı kopyalamaz; aynı nesne paylaşılır.
  • C yanlış: Zincirleme nitelik erişimi geçerlidir.
  • D yanlış: name niteliği bir metne bağlıdır.

Madde 2 — Doğru: A. _items Product nesnelerine referans tutar; total() her nesnenin price niteliğini toplar.

  • B yanlış: Toplanan fiyatlardır, eleman sayısı değil.
  • C yanlış: Toplanan nesneler değil, product.price değerleridir.
  • D yanlış: total() bir sayı döndürür.
  • E yanlış: İki ürün gerçekten eklenmiştir.

Madde 3 — Doğru: B. Sınıfın kontrol kapısı dolaşılır. 3. haftadaki aliasing ile 8. haftadaki kontrollü arayüz fikri burada birleşir.

  • A yanlış: Doğrudan append çağrısı metodu hiç çalıştırmaz.
  • C yanlış: Ekleyebilir; sorun da budur.
  • D yanlış: Sıra bozulmaz; sorun kuralların atlanmasıdır.
  • E yanlış: Normal bir örnek niteliğidir ve erişilebilir.

Madde 4 — Doğru: C. Property yalnızca erişim biçimini değiştirir. Döndürülen nesne hâlâ sınıfın iç listesidir.

  • A yanlış: Böyle bir otomatik kopyalama yoktur.
  • B yanlış: Property dönen nesneyi dondurmaz.
  • D yanlış: Alt çizgi yalnız bir sözleşmedir; teknik engel değildir.
  • E yanlış: Setter atamayı düzenler; döndürülen liste üzerinden yapılan değişikliği engellemez.

Madde 5 — Doğru: D. Demet eleman eklemeye ve çıkarmaya izin vermez; çağıran içeriği okuyabilir ama koleksiyonu değiştiremez.

  • A yanlış: Yalnız sayıyı verir; içeriği okumaya izin vermez.
  • B yanlış: Bağımsız kopya verir ama yine değiştirilebilir bir liste döndürür; okunabilirlik sözleşmesini ifade etmez.
  • C yanlış: Aynı mutable listeyi verir; dış kod append yapabilir.
  • E yanlış: Listeyi boşaltır ve None döndürür.

Madde 6 — Doğru: E. Delegasyon, kuralı kopyalamak yerine sahibine yönlendirmektir. İndirim kuralı değişirse yalnız Product güncellenir.

  • A yanlış: Aralarında kalıtım değil, kompozisyon vardır.
  • B yanlış: Böyle bir kısıt yoktur.
  • C yanlış: Yazılabilir ve çalışır; sorun tasarımdadır.
  • D yanlış: Delegasyon mekanik bir kural değildir; basit ara toplam yerinde de kalabilir.

Madde 7 — Doğru: A. Book.borrow() ve Member.add_borrowed_book() gibi davranışlar kendi nesnelerine aittir; Library işlemi yalnız koordine etmelidir.

  • B yanlış: Kurallar sahibi nesnelerde dururken koordinasyon ayrı bir iştir.
  • C yanlış: Her üyenin kendi sayısı olmalıdır; sınıf niteliği yanlış olurdu.
  • D yanlış: Alabilir; sorun parametrelerde değil sorumluluk dağılımındadır.
  • E yanlış: Python’da değiştirilebilir; asıl sorun kuralın nerede durduğudur.

Madde 8 — Doğru: B. Her nesne kendi durumunun kurallarını korur; koordinasyon sınıfı sırayı yönetir.

  • A yanlış: Sorumluluk dağılımını büsbütün kaybettirir.
  • C yanlış: Aynı merkezî kural yığını bu kez fonksiyona taşınır.
  • D yanlış: Erişimi düzenler ama davranışın hangi nesneye ait olduğunu değiştirmez.
  • E yanlış: Kütüphane bir kitap ya da üye değildir; kalıtım yanlış modeldir.

Madde 9 — Doğru: C. Kullanım biçimi “numaradan nesneye erişim” ise sözlük bu işi doğrudan yapar.

  • A yanlış: Üye eklemek gerekir; demet buna izin vermez.
  • B yanlış: Her aramada listeyi baştan taramak gerekir.
  • D yanlış: Küme üyelik bilgisi verir ama numaradan nesneye erişim sağlamaz.
  • E yanlış: Üye sayısı önceden bilinmediği için uygulanabilir değildir.

Madde 10 — Doğru: D. Tek bir üyenin kendi numarasının benzersizliğini bilmesi mümkün değildir; bu kural topluluğu tutan yerde durur.

  • A yanlış: Numara her üyeye özgüdür.
  • B yanlış: Bu bir iş kuralıdır; raise uygundur.
  • C yanlış: Kontrol eşitliğe değil, anahtarın varlığına bakar.
  • E yanlış: Sözlük ikinci yazımda değeri sessizce ezer; bu kontrol onu engeller.

Tek sayfa özet

  • Gerçek sistemler nesnelerin işbirliğiyle kurulur.
  • Nesne koleksiyonları da nesnenin durumudur; kapsülleme ilkeleri onlara da uygulanır.
  • Mutable iç koleksiyonu dışarı açmak aliasing yoluyla invariant’ları deldirebilir.
  • Delegation, işi uygun sorumluluğa devretmektir.
  • Bir-çok ilişkilerde koleksiyon seçimi ve benzersizlik anahtarı kullanım ihtiyacına göre belirlenir.
  • Kalıtım tek yeniden kullanım mekanizması değildir.

Bu bölümün kazanımları

Bu bölümü bitiren öğrenci:

  • Bir nesnenin başka bir nesneyi tuttuğu has-a ilişkisini kurar.
  • Nesne koleksiyonunun nesnelerin kendisini değil referanslarını tuttuğunu belirler.
  • İç mutable koleksiyonu dışarı açmanın kapsüllemeyi deldiğini gösterir.
  • Okunabilir görünüm için demet veya kopya döndürme seçeneklerini karşılaştırır.
  • Davranışı kuralın sahibine bırakarak delegasyon uygular.
  • Sorumluluğu yanlış sınıfa yığmanın “her şeyi yapan merkez” ürettiğini açıklar.
  • Bir-çok ilişkide liste ile sözlük seçimini kullanım biçimine göre yapar.
Back to top