Kompozisyon, delegation ve nesne koleksiyonları

Gerçek programlar tek bir sınıftan oluşmaz. Bir siparişin müşterisi ve ürünleri, bir kütüphanenin kitapları ve üyeleri vardır. Bu hafta bir nesnenin başka nesneleri niteliklerinde tutmasını (has-a, yani “sahiptir” ilişkisi), sınıfları bu yolla birleştirmeyi (kompozisyon) ve bir nesnenin bir hesabı, o hesabın kuralını bilen başka bir nesneye bırakmasını (delegation) göreceğiz.

Bu sayfadaki kod hücreleri kendiliğinden çalışmaz. Her hücrede önce çıktıyı tahmin edin, sonra Run Code düğmesine basın. Kodu değiştirip yeniden çalıştırabilir, Start Over ile ilk hâline döndürebilirsiniz.

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ı
  • Değiştirilebilir (mutable) iç koleksiyonu dışarı vermenin kapsüllemeyi bozması
  • İç listeyi dışarıya demet ya da kopya olarak vermek
  • Davranışı kuralın sahibine bırakan delegation
  • Sorumluluğu yanlış sınıfa yığmanın sonucu
  • Bire çok ilişkide liste ile sözlük seçimi
  • UML’deki aggregation ve composition ayrımının biçimsel tanımı
  • ORM (nesneleri veritabanı tablolarına eşleyen araçlar) ve kalıcı veri katmanı
  • collections.abc koleksiyon protokolleri

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

Sipariş programındaki beş işi C’de nasıl yapardınız?

Bir internet mağazasının sipariş programını yazıyorsunuz. Her siparişin bir müşterisi ve birkaç ürünü var. Program şu beş işi yapacak:

  1. Siparişin müşterisini ve ürünlerini tutup toplam tutarı hesaplamak
  2. Ürünleri siparişe yalnız kontrolden geçirerek eklemek: aynı ürün iki kez eklenmemeli
  3. Toplu alımda ürünün indirim kuralını uygulamak: 10 adet ve üstüne yüzde 10 indirim
  4. Sipariş verilince ürünün stoğunu düşmek
  5. Müşteriyi numarasıyla bulmak

C’de müşteriyi ve ürünü birer struct ile tutardınız. Siparişin struct’ında bir müşteri ve bir ürün dizisi olurdu. Bu işlerin her birini C’de nasıl yapardınız? Okumaya devam etmeden önce her iş için bir satır yazın. 8. haftadaki Product sınıfıyla da düşünebilirsiniz. “Ürün dizisini döngüyle dolaşıp fiyatları toplarım” da bir cevap.

Bölümde bu beş işin Python’da nasıl yazıldığını göreceğiz. Bölümün sonunda listenize döneceğiz.

Bir nesne başka nesneyi tutabilir

C’de bir struct’ın içine başka bir struct koyduğunuzda iç yapının alanları kopyalanırdı. Hücredeki Order, bir Customer nesnesini customer niteliğinde tutuyor. Sipariş müşterinin bir kopyasını mı tutuyor, yoksa aynı nesneyi mi? Son satır ne yazar?

self.customer = customer ataması kopya almaz: 3. haftada gördüğünüz gibi = var olan nesneye bir ad daha bağlar. C’de aynı davranışı struct’ın içinde müşteriye bir işaretçi tutarak elde ederdiniz.

İngilizcede bu ilişki Order has a Customer (sipariş bir müşteriye sahiptir) diye okunur. has-a adı buradan gelir. Bir sınıfın işini, niteliklerinde tuttuğu başka nesnelerle yapmasına kompozisyon (composition) denir.

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ü geniş anlamda kullanıyoruz: bir sınıfın işini, niteliklerinde tuttuğu başka nesnelerle yapması. UML (sınıfları ve aralarındaki ilişkileri kutu ve okla çizmek için kullanılan standart gösterim) ve bazı kaynaklar burada iki ilişkiyi ayırır. Parça bütünle birlikte yok oluyorsa, örneğin sipariş silinince sipariş satırları da gidiyorsa buna composition (bileşim) denir. Parça bütünden bağımsız yaşıyorsa, örneğin takım dağılınca oyuncular yerinde kalıyorsa buna aggregation (agregasyon) denir. Bu ayrım sınavda sorulmaz. Nesneler arasındaki ilişkiyi doğru kurmanız ve her kuralı doğru sınıfa koymanız yeter.

Nesne koleksiyonu

C’de siparişin ürünlerini bir struct dizisinde tutar, toplamı döngüyle hesaplardınız. Python’da sipariş ürünleri bir listede tutar. Hücrede klavye siparişe eklendikten sonra fiyatı 1000’den 900’e iniyor. order.total() kaç döndürür?

_items bir sayı listesi değildir, Product nesnelerinin referanslarını tutar. keyboard adı da listedeki ilk eleman da aynı nesneyi gösterir. total() her çağrıldığında ürünlerin o anki fiyatına bakar.

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

Nesne koleksiyonu

İç listeyi dışarı vermek kapsüllemeyi bozabilir

Siparişe ürün eklemenin yolu add_product() olmalı, çünkü kontrol orada. Hücredeki add_product aynı ürünün iki kez eklenmesini engelliyor. Kısa kalsın diye ürünleri adlarıyla tutuyoruz. Liste _items adıyla duruyor ve dışarıya bir property ile veriliyor. Dışarıdaki kod order.items.append("Klavye") yazıyor. Bu satır hata verir mi? Son satır ne yazar?

Property listeyi olduğu gibi döndürdü. Dış kod sınıfın listesine referans aldı ve add_product() metoduna hiç uğramadan ekleme yaptı. items düz bir nitelik olsaydı (self.items = []) sonuç yine aynı olurdu.

Dış kodun listeyi yalnız okuyabilmesini istiyorsanız bir demet döndürebilirsiniz. Bu kez property demet döndürüyor. Son satırda ne olur?

Demetin append metodu yoktur. Dış kod listeyi okuyabilir, ama eleman eklemenin tek yolu yine add_product().

Dış kod üzerinde değişiklik yapabileceği ayrı bir liste istiyorsa sığ kopya döndürebilirsiniz. Kopyaya eklenen eleman iç listeye gitmez:

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

Hangisinin uygun olduğu, sınıfın çağırana ne söz verdiğine bağlıdır. Demet “bu listeyi değiştirmeyin” der, kopya “bu liste artık sizin” der.

Important3. ve 8. haftadaki iki fikir burada buluşuyor

3. haftada aliasing gördük: iki ad aynı değiştirilebilir nesneyi gösterince birinden yapılan değişiklik ötekinden de görünür. 8. haftada nesnenin durumunu yalnız sınıfın kendi metotlarıyla değiştirmeyi öğrendik, çünkü kurallar o metotlarda denetlenir. İç listeyi dışarı verdiğinizde dış kod sınıfın listesine referans alır. Artık add_product() gibi denetleyen metotlara hiç uğramadan listeyi değiştirebilir.

Delegation: işi sahibine bırakmak

Sipariş satırı (OrderLine) bir ürünü ve adedini tutar. Toplu alım indirimi ise ürünün fiyat kuralıdır: Product.price_for() metodu 10 adet ve üstüne yüzde 10 indirim uygular. Satırın tutarını hesaplayan subtotal() bu metodu çağırıyor. İki satır ne yazar?

5 kalemde indirim yoktur, discount 0 olur ve sonuç 10 * 5 * 1 = 50 çıkar. 12 kalemde discount 0.10 olur. 1 - 0.10 bir ondalık sayı (float) olduğu için sonuç da float olur: 108.0.

OrderLine fiyat kuralını kopyalamaz, hesabı Product.price_for() metoduna devreder. Bir nesnenin bir işi, o işin kuralını bilen başka bir nesneye bırakmasına delegation denir. İndirim kuralı değişirse yalnız Product sınıfını güncellersiniz. C’de satır tutarını hesaplayan fonksiyonun, ürünün fiyat fonksiyonunu çağırması gibi düşünebilirsiniz.

ImportantHer çağrıyı devretmek gerekmez

Delegation, davranışı kuralın sahibi olan sınıfa koyar. İndirim kuralı ürüne aitse hesabı Product yapar. Ürüne özgü bir kural yoksa, örneğin ara toplam yalnız birim fiyatla adedin çarpımıysa, bu çarpmayı OrderLine içinde yapmak da makuldür.

Sorumluluk yanlış yerdeyse ne olur?

Sipariş verilince ürünün stoğu düşmeli. Bu işi Order kendisi yapabilir:

class Order:
    def add_line(self, product, quantity):
        if quantity > product._stock:
            raise ValueError("Yetersiz stok")
        product._stock -= quantity
        self._lines.append(OrderLine(product, quantity))
        # kargo ücreti, müşteri puanı, kampanya kuralları...

Kod çalışır, ama stok kuralı artık iki yerde yazılı: 8. haftada aynı kuralı Product.sell() içine yazmıştık. Order ürünün alt çizgili niteliğine dışarıdan dokunuyor. Kargo, puan ve kampanya kuralları da buraya eklendikçe sınıf kısa sürede “her şeyi yapan” bir merkeze dönüşür. Stoğu düşmek ürünün işidir:

class Order:
    def add_line(self, product, quantity):
        product.sell(quantity)
        self._lines.append(OrderLine(product, quantity))

Order artık yalnız işlemin sırasını yönetir: önce ürüne satışı yaptırır, sonra satırı ekler. Stok yetmezse sell() hata verir ve satır da eklenmez.

Bire çok ilişki ve koleksiyon seçimi

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

Bir siparişin tek müşterisi vardır, onu tek bir nitelik tutar. Satırları ise çoktur, onları bir koleksiyon tutar. Bir nesnenin aynı türden çok sayıda nesne tuttuğu bu ilişkiye bire çok ilişki denir. Koleksiyonun liste mi sözlük mü olacağına, ona nasıl erişeceğiniz karar verir.

Mağazanın müşterilerini tutan bir Shop sınıfı düşünün. C’de müşteriyi numarasıyla bulmak için diziyi baştan sona tarardınız. Sözlükte ise anahtar müşteri numarası, değer Customer nesnesi olur ve müşteriye taramadan ulaşırsınız. Hücrede 103 numaralı müşteri yok. Üç satır ne yazar?

Aynı numaranın iki kez eklenmesini Shop engeller, çünkü bütün numaraları yalnız koleksiyonu tutan sınıf görür. Tek bir Customer nesnesi öteki müşterileri bilmez.

Soruya dönelim: beş iş Python’da

Bölümün başında sipariş programının beş işini C’de nasıl yapacağınızı sormuştuk. Python’daki karşılıkları şöyle:

İş C’de Bu haftaki Python yolu Dikkat
Müşteriyi ve ürünleri tutup toplamı hesaplamak struct içinde müşteri ve ürün dizisi, toplam için döngü self.customer niteliği, self._items listesi ve total() metodu Liste ürünlerin kopyasını değil referansını tutar. Ürünün fiyatı değişirse toplam da değişir
Ürünü yalnız kontrolden geçirerek eklemek add_item() fonksiyonu yazılır, ama diziye her kod yazabilir add_product() metodu ve _items listesi Property self._items döndürürse dış kod kontrolü atlar. Demet ya da kopya döndürün
İndirim kuralını uygulamak Satır tutarını hesaplayan fonksiyon ürünün fiyat fonksiyonunu çağırır subtotal() içinde self.product.price_for(self.quantity) Kuralı satıra kopyalamayın, ürüne bırakın
Stoğu düşmek Sipariş fonksiyonu ürünün stok alanını azaltır add_line() içinde product.sell(quantity) Order ürünün _stock niteliğine dokunmaz
Müşteriyi numarasıyla bulmak Diziyi baştan sona tararsınız self._customers sözlüğü ve find_customer() Tekrar kontrolünü koleksiyonu tutan Shop yapar

Listenizi tabloyla karşılaştırın. 1. iş için “müşteri struct’ını siparişin içine koyarım” diye yazdıysanız C’de müşterinin bir kopyasını tutmuş olurdunuz: müşterinin adı değişince sipariş eski adı gösterirdi. Python’da sipariş aynı müşteri nesnesini gösterir. 2. işte C ile Python aynı yerde zorlanır: diziyi ya da listeyi dışarı açarsanız kontrolü atlayan kodu hiçbir şey durdurmaz. Python’da çözüm, listeyi dışarı hiç vermemek ya da demet veya kopya olarak vermektir. 4. iş için “sipariş fonksiyonu stoğu azaltır” dediyseniz kod çalışır, ama stok kuralı iki yerde yazılmış olur.

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

Car ve Engine, Order ve Customer, Playlist ve Song, Student ve Course çiftlerine tek tek bakın. İlişki has-a mı? Öyleyse hangi sınıfın ötekini tutması uygun? Tartışın. Sonra şunu düşünün: ilişkiyi iki tarafta birden saklarsanız, yani öğrenci derslerini, ders de öğrencilerini tutarsa, bir öğrenci dersi bıraktığında hangi iki yeri güncellemeniz gerekir? İkisinden birini unutursanız ne olur?

Alıştırma: Nesne koleksiyonunu tamamla

Team sınıfı oyuncuları bir listede tutuyor. add_player aynı numaralı oyuncunun iki kez eklenmesini engelliyor. Kontroldeki any(...), parantezin içindeki koşul listedeki oyunculardan en az biri için doğruysa True döndürür. Kontrol geçtikten sonra oyuncuyu iç listeye ekleyen satırı boşluğa yazın.

self._players.append(player)

Sıra sizde: Kütüphane modeli

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

  • Her kitap ISBN’iyle ayırt edilsin.
  • Aynı ISBN iki kez eklenemesin.
  • Üye numarası benzersiz olsun.
  • Library kitapları ve üyeleri koleksiyonlarda tutsun.
  • Library bu değiştirilebilir koleksiyonları doğrudan dışarı vermesin.
  • Ödünç alma davranışı hangi sınıfa ya da sınıflara ait? Gerekçesiyle karar verin.

Çalışır kod görevi: İki kitap ve iki üye ekleyin. Aynı ISBN’i ya da aynı üye numarasını ikinci kez eklemeye çalışınca hata alındığını assert ve try/except ile doğrulayın. Ayrıca dışarıya verdiğiniz koleksiyona append() yapılamadığını ya da yapılan eklemenin iç koleksiyona yansımadığını gösterin.

Sınav provası

Önce kendi cevabınızı seçin; sonra cevap anahtarında her şıkkın neden doğru veya yanlış olduğunu okuyun. Ç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):
        result = 0
        for product in self._items:
            result += product.price
        return result

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

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. append başka bir sınıfın listesine nesne ekleyemez
  • C. Dışarıdan yapılan ekleme listenin sırasını bozduğu için
  • D. add_product() atlandığı için içindeki kontroller çalışmaz
  • 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. Hayır; aynı liste nesnesine referans verdiği için dış kod yine append yapabilir
  • C. Evet; property ile döndürülen değerler salt okunur olur
  • 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. İç listeyi dışarıya yalnızca okunacak biçimde vermek isteyen property hangisini döndürmelidir?

  • A. return len(self._items)
  • B. return tuple(self._items)
  • C. return list(self._items) (kopya)
  • D. return 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. Fiyat kuralı ürüne aittir; OrderLine hesabı ürüne devreder
  • D. Hesap OrderLine içinde yazılırsa AttributeError oluşacağı için
  • E. Delegation her çağrıda zorunlu olduğu için

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. Sorun yoktur; koordinasyon sınıfı bütün kuralları taşımalıdır
  • B. borrowed_count bütün üyeler için ortak bir sınıf niteliği olmalıdır
  • C. Library kitap ve üyenin kurallarını da taşıyan bir merkeze dönüşü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. Kuralları Book ve Member metotlarına taşıyıp Library içinde çağırmak
  • B. Book ve Member sınıflarını kaldırıp her şeyi sözlükte tutmak
  • C. borrow_book metodunu modül düzeyinde bir fonksiyona taşımak
  • D. is_borrowed ve borrowed_count niteliklerini property yapmak
  • E. Library sınıfını Book ve Member sınıflarından türetip kuralları devralmak

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

  • A. Sözlük; anahtar üye numarası, değer Member nesnesi olur
  • B. Demet; üye listesi dışarıdan değiştirilmemelidir
  • C. Liste; üyeler eklenme sırasıyla durduğu için arama kolaydır
  • D. Küme; her üye numarası yalnız bir kez bulunabildiği için
  • E. Her üye için Library üzerinde ayrı bir nitelik

Madde 10. Tekrar kontrolünün aşağıdaki gibi add_member metodunda 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. Tekrar kuralının, bütün üyeleri tutan sınıfa ait olduğunu
  • E. Sözlüğün aynı anahtarı iki ayrı kayıt olarak saklayabildiğini

Madde 1. Doğru: E. Kompozisyonda nesne kopyalanmaz, referansı tutulur. customer.name değişince order.customer.name da yeni değeri verir.

  • 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: E. _items Product nesnelerine referans tutar; total() her nesnenin price niteliğini toplar.

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

Madde 3. Doğru: D. Dış kod iç listeye doğrudan erişir ve add_product() metoduna hiç uğramadan ekleme yapar; metottaki kontroller de çalışmaz. 3. haftadaki aliasing ile 8. haftadaki kapsülleme fikri burada buluşur.

  • A yanlış: Metodun içinde de append yazdığı için iki yolun aynı olduğu sanılabilir. Oysa doğrudan append çağrısı metodu hiç çalıştırmaz.
  • B yanlış: Liste başka bir nesneye ait olduğu için ona dışarıdan eklenemeyeceği sanılabilir. Ekleyebilir; sorun da budur.
  • C yanlış: Eleman beklenmedik bir yerden girdiği için sıranın bozulduğu düşünülebilir. append elemanı yine sona ekler; sorun kuralların atlanmasıdır.
  • E yanlış: Property ile erişim görmüş öğrenci, düz niteliğe dışarıdan erişimin hata vereceğini düşünebilir. Ama items sıradan bir örnek niteliğidir; ona erişmek hata vermez.

Madde 4. Doğru: B. 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.
  • C yanlış: Property, döndürdüğü listeyi değiştirilemez hâle getirmez.
  • D yanlış: Alt çizgi yalnız bir adlandırma geleneğidir; teknik engel değildir.
  • E yanlış: Setter yalnızca order.items = ... gibi atamaları denetler; döndürülen listeye yapılan append çağrısını engellemez.

Madde 5. Doğru: B. 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.
  • C yanlış: İç listeyi korur ama çağırana değiştirilebilir bir liste verir; “bu yalnızca okunur” mesajını taşımaz.
  • D yanlış: Aynı değiştirilebilir listeyi verir; dış kod append yapabilir.
  • E yanlış: Listeyi boşaltır ve None döndürür.

Madde 6. Doğru: C. Delegation’da kural kopyalanmaz; hesap kuralın sahibine bırakılır. İndirim kuralı değişirse yalnız Product güncellenir.

  • A yanlış: price_for() çağrısı devralınmış bir metot gibi görünebilir. Ama OrderLine bir Product nesnesini nitelik olarak tutar; aralarında kalıtım değil, kompozisyon vardır.
  • B yanlış: Gövdede çarpma yerine bir metot çağrısı görülünce çarpmanın yasak olduğu sanılabilir. Böyle bir kısıt yoktur.
  • D yanlış: Fiyat Product içinde durduğu için OrderLine içinden ona ulaşılamayacağı sanılabilir. self.product.price ile ulaşılır ve kod çalışır; sorun tasarımdadır.
  • E yanlış: Örnekte hesap devredildiği için bunun zorunlu olduğu sanılabilir. Her çağrıyı devretmek gerekmez; ürüne özgü bir kural yoksa basit ara toplam OrderLine içinde de kalabilir.

Madde 7. Doğru: C. Library her nesnenin iç durumunu doğrudan yönetir ve “her şeyi yapan” sınıf olur. Book.borrow() ve Member.add_borrowed_book() gibi davranışlar kendi nesnelerine aittir; Library yalnızca bu metotları sırayla çağırmalıdır.

  • A yanlış: İşlemi Library yönettiği için bütün kuralların da orada durması gerektiği sanılabilir. Kurallar ait oldukları nesnelerde durmalıdır; işlemin sırasını yönetmek ayrı bir iştir.
  • B yanlış: Sayaç kütüphanedeki bütün ödünçleri sayıyormuş gibi okunursa ortak bir nitelik akla gelir. Ama her üyenin kendi sayısı olmalıdır; sınıf niteliği bütün üyelerin sayısını birleştirirdi.
  • D yanlış: İki farklı sınıftan nesne alan metot karışık göründüğü için sorun orada sanılabilir. Metot birden çok nesne alabilir; sorun parametrelerde değil sorumluluk dağılımındadır.
  • E yanlış: Kapsülleme konusu, dışarıdan atamanın engellendiği izlenimini bırakabilir. Python’da nitelik dışarıdan değiştirilebilir; sorun, kuralın yanlış sınıfta durmasıdır.

Madde 8. Doğru: A. Book.borrow() ve Member.add_borrowed_book() gibi metotlarla her nesne kendi durumunun kurallarını korur. Library yalnızca bu metotları sırayla çağırır ve işlemin sırasını yönetir.

  • B yanlış: Sınıf sayısı azaldığı için tasarım sadeleşmiş gibi görünür. Oysa kurallar hiçbir nesneye ait olmaz; sorumluluk dağılımı büsbütün kaybolur.
  • C yanlış: Metodu sınıftan çıkarmak Library sınıfını küçülttüğü için çözüm gibi görünür. Ama aynı merkezî kural yığını bu kez fonksiyona taşınır.
  • D yanlış: Property doğrulama için kullanıldığından burada da çözüm gibi görünür. Ama property erişimi düzenler, davranışın hangi nesneye ait olduğunu değiştirmez.
  • E yanlış: Kalıtım, kitap ve üye davranışlarını tek yerde toplamanın kısa yolu gibi görünür. Ama kütüphane bir kitap ya da üye değildir; kalıtım yanlış modeldir.

Madde 9. Doğru: A. Kullanım biçimi “numaradan nesneye erişim” ise sözlük bu işi doğrudan yapar: self._members[member_id] listeyi taramadan üyeyi verir.

  • B yanlış: İç listeyi dışarıya demet olarak vermek, demetin burada da uygun olduğunu düşündürebilir. Ama üye eklemek gerekir; demet buna izin vermez.
  • C yanlış: Liste en tanıdık koleksiyon olduğu ve sırayı koruduğu için ilk akla gelen seçimdir. Ama numarayla aramada her seferinde listeyi baştan taramak gerekir.
  • D yanlış: Üye numaraları benzersiz olduğu için küme uygun görünebilir. Küme bir değerin içinde olup olmadığını söyler; ama numarayı verip üye nesnesini alamazsınız.
  • E yanlış: Birkaç üyeyle self.member_1 gibi nitelikler işe yarıyormuş gibi görünür. Üye sayısı önceden bilinmez; her yeni üye için sınıfa nitelik yazılamaz.

Madde 10. Doğru: D. Tek bir Member nesnesi öteki üyeleri görmediği için numarasının benzersiz olup olmadığını bilemez. Koleksiyonda tekrar olmamasını, koleksiyonu yöneten sınıf sağlar.

  • A yanlış: Numara üyeyi tanıtan bir bilgi olduğu için sınıf düzeyine ait sanılabilir. Ama numara her üyeye özgüdür; örnek niteliği olarak kalmalıdır.
  • B yanlış: assert de bir koşulu denetlediği için aynı işi görecekmiş gibi görünür. Ama assert testlerde beklenen sonucu doğrulamak için kullanılır. Aynı numaranın ikinci kez gelmesi programın olağan çalışmasında karşılaşılabilecek bir durumdur; çağırana raise ValueError ile bildirilir.
  • C yanlış: Tekrar denetimi “iki üye aynı mı?” sorusuna benzediği için eşitlik metodu akla gelir. Ama kontrol eşitliğe değil, anahtarın sözlükte olup olmadığına bakar.
  • E yanlış: Kontrol yazıldığına göre sözlüğün tekrara izin verdiği sanılabilir. Oysa sözlük aynı anahtarı iki kez saklamaz; ikinci yazımda eski değeri hata vermeden ezer. Bu kontrol o ezilmeyi engeller.

Tek sayfa özet

  • Bir nesne başka bir nesneyi niteliğinde tutabilir (has-a). Python nesneyi kopyalamaz, aynı nesneye referans tutar.
  • Nesne koleksiyonu da nesnelerin referanslarını tutar. Listedeki ürünün fiyatı değişirse siparişin toplamı da değişir.
  • Bir nesnenin tuttuğu liste ya da sözlük de o nesnenin durumudur. Kapsülleme kuralları onlar için de geçerlidir.
  • İç listeyi olduğu gibi dışarı verirseniz dış kod, aliasing yüzünden sınıfın kontrollerini atlayabilir. Yalnız okunacaksa demet, çağıran düzenleyecekse kopya döndürün.
  • Delegation, bir işi o işin kuralını bilen nesneye bırakmaktır. Kural tek sınıfta kalır.
  • Bir sınıf öteki nesnelerin niteliklerini kendisi değiştirirse kurallar o sınıfta birikir. Her nesne kendi durumunu kendi metoduyla değiştirsin.
  • Bire çok ilişkide liste mi sözlük mü kullanacağınızı, koleksiyona nasıl erişeceğiniz belirler. Numarasıyla bulunacak nesneler için sözlük kullanın.
  • Başka bir sınıfın davranışından yararlanmanın tek yolu kalıtım (gelecek hafta) değildir. Kompozisyonla o sınıfın nesnesini tutup metodunu çağırabilirsiniz.

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.
  • Değiştirilebilir iç koleksiyonu dışarı vermenin kapsüllemeyi bozduğunu gösterir.
  • İç listeyi dışarıya demet ya da kopya olarak verme seçeneklerini karşılaştırır.
  • Davranışı kuralın sahibine bırakarak delegation uygular.
  • Sorumluluğu yanlış sınıfa yığmanın o sınıfı “her şeyi yapan merkeze” dönüştürdüğünü açıklar.
  • Bire çok ilişkide liste ile sözlük seçimini kullanım biçimine göre yapar.
Back to top