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 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ı başka nesneye bırakmasını (delegation) göreceğiz.
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ı
Mutable iç koleksiyonu dışarı vermenin kapsüllemeyi bozması
İç listeyi dışarıya demet ya da kopya olarak verme
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; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Bu davranış hangi nesnenin işi?
Bir sipariş, ürünlerin fiyat kuralını (örneğin “10 adet ve üstüne yüzde 10 indirim”) kendisi mi yazmalı, yoksa fiyatı ürüne mi sormalı? Kütüphane, ödünç verdiği kitabın durumunu kendisi mi değiştirmeli? Her iki soruda da iki seçenek de çalışır. Hangisini seçeceğinizi, kuralın hangi sınıfa ait olduğu belirler.
Bir nesne başka nesneyi tutabilir
Order, bir Customer nesnesini customer niteliğinde tutar. İngilizcede bu ilişki Order has a Customer (sipariş bir müşteriye sahiptir) diye okunur; has-a adı buradan gelir.
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ı (has-a ilişkisi). 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ı bilmek işinize yarar. Ancak bu hafta alıştırmalarda ve sınavda UML ayrımı sorulmaz. Sizden beklenen, nesneler arasındaki ilişkiyi doğru kurmanız ve her kuralı doğru sınıfa koymanızdır.
Nesne koleksiyonu
_items bir sayı listesi değildir; Product nesnelerinin referanslarını tutar.
İç listeyi dışarı vermek kapsüllemeyi bozabilir
Ş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 olabilirself.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. Üstelik iç listeyi olduğu gibi döndüren bir property de sorunu çözmez, çünkü dışarıya yine aynı liste nesnesinin referansını verir:
@propertydef items(self):returnself._items # dış kod yine append yapabilir
Dış kodun listeyi yalnızca okuyabilmesini istiyorsanız bir demet döndürebilirsiniz:
@propertydef items(self):returntuple(self._items)
Dış kod üzerinde değişiklik yapabileceği ayrı bir liste istiyorsa sığ kopya döndürebilirsiniz. Kopyaya eklenen eleman iç listeye gitmez:
returnself._items.copy()
Hangisinin uygun olduğu arayüz sözleşmesine, yani 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ı mutable nesneyi gösterince birinden yapılan değişiklik ötekinden de görünür. 8. haftada nesnenin durumunu yalnızca 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’da amaç, davranışı kuralın sahibi olan sınıfa koymaktır. İndirim kuralı ürüne aitse hesabı Product yapar. Ürüne özgü bir kural yoksa, örneğin ara toplam yalnızca birim fiyatla adedin çarpımıysa, bu çarpmayı OrderLine içinde yapmak da makuldür.
Library kitabın ve üyenin niteliklerini kendisi değiştirirse kitapla ve üyeyle ilgili bütün kurallar zamanla bu sınıfta birikir. Sınıf kısa sürede “her şeyi yapan” bir merkeze dönüşür. Oysa kitabı “ödünçte” diye işaretlemek Book.borrow() metodunun, üyenin ödünç sayısını artırmak Member.add_borrowed_book() metodunun işi olabilir. Library ise yalnızca işlemin sırasını yönetir ve bu iki metodu çağırır.
Nesne koleksiyonu
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. Bir üyeyi numarasıyla bulmanız gerekiyorsa sözlük doğal bir seçimdir, çünkü listeyi baştan sona taramadan doğrudan o üyeye ulaşırsınız:
Aynı numaranın iki kez eklenmesini Library engeller, çünkü bütün numaraları yalnızca koleksiyonu tutan sınıf görür. Tek bir Member nesnesi öteki üyeleri bilmez.
Alıştırma: İlişkiyi belirle
Car–Engine, Order–Customer, Playlist–Song, Student–Course çiftlerini tek tek ele alın. İlişki has-a mı? Öyleyse hangi sınıfın ötekini tutması makul? 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
self._players.append(player)
Sıra sizde: Kütüphane modeli
Book, Member, 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 mutable 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/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.
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 inself._members:raiseValueError("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
TipCevaplar ve gerekçeler
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._itemsProduct 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 kontrollü arayüz 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 sözleşmedir; 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ı mutable listeyi verir; dış kod append yapabilir.
E yanlış: Listeyi boşaltır ve None döndürür.
Madde 6. Doğru: C. Delegasyonda 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 bu bir iş kuralıdır ve her çalıştırmada korunmalıdır; raise uygundur.
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
Gerçek programlarda nesneler birbirini tutar ve birbirine iş bırakır.
Bir nesnenin tuttuğu liste ya da sözlük de o nesnenin durumudur; kapsülleme kuralları onlar için de geçerlidir.
Mutable iç koleksiyonu dışarı verirseniz aliasing yüzünden dış kod, sınıfın koruduğu değişmez koşulları (invariant) atlayabilir.
Delegation, işi o işten sorumlu nesneye devretmektir.
Bire çok ilişkide liste mi sözlük mü kullanacağınızı ve hangi alanı anahtar yapacağınızı, koleksiyona nasıl erişeceğiniz belirler.
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.
Mutable 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.