Gözetimli bütünleştirici nesne tasarımı

Son hafta yeni bir OOP kavramı eklemiyoruz. Amaç, dönem boyunca öğrendiğimiz fikirleri küçük ve sınırları açık bir değişiklikte birlikte kullanmak: nesne durumunu anlamak, sorumluluğu doğru sınıfa vermek, geçerli durumu korumak, nesneler arası ilişkiyi yönetmek ve değişikliğin etkisini açıklamak.

Bu hafta neleri yapabilmelisiniz?

  • hazır küçük bir sınıf modelini okuyabilmeli,
  • yeni gereksinimin hangi sınıfın sorumluluğuna ait olduğunu belirleyebilmeli,
  • gereksiz yeni sınıf veya kalıtım oluşturmadan davranış ekleyebilmeli,
  • invariant’ı koruyan değişiklik yapabilmeli,
  • birden fazla nesneyi etkileyen değişikliklerde tutarlı durumu koruyabilmeli,
  • kimlik/eşitlik ve nesne koleksiyonu sorunlarını kontrol edebilmeli,
  • değişikliğin nedenini ve sınırını kısa teknik dille açıklayabilmelisiniz.

Başlangıç sistemi

Aşağıdaki küçük kütüphane modelini inceleyin:

WarningBaşlangıç kodu bilinçli olarak tamamlanmış tasarım değildir

Yukarıdaki Library.borrow() başarılı akışta çalışır, ancak iki nesneyi art arda değiştirdiği için bütün hata durumlarında tutarlılığı henüz garanti etmez. Örneğin book.borrow_by(member) başarılı olduktan sonra member.add_book(book) hata verirse sistem yarım güncellenmiş kalabilir.

Bu eksiklik bilinçlidir: aşağıdaki ilk gereksinimde önce bütün ön koşulları doğrulayıp sonra durum değişikliği yapma ihtiyacını bu kod üzerinden keşfedeceğiz. Başlangıç örneğini üretim için güvenli son tasarım olarak kabul etmeyin.

Önce sistemi okuyun

Kod eklemeden şu soruları cevaplayın:

  1. Book hangi durumu koruyor?
  2. Member hangi koleksiyonu yönetiyor?
  3. Library neden sözlük kullanıyor?
  4. Library.borrow() hangi işleri kendi yapıyor, hangilerini başka nesnelere devrediyor?
  5. Aynı Book nesnesi hem Library.books içinde hem Member.borrowed_books listesinde bulunabilir mi? Bu durumda iki ayrı kitap mı vardır?
  6. Library.borrow() içindeki iki durum değişikliğinden ikincisi başarısız olursa ne olur?

Son iki soru 6. haftadaki referans zihinsel modeline ve 8–9. haftalardaki kapsülleme/delegation fikirlerine geri döner: aynı nesne birden fazla yerden erişilebilir ve birden fazla nesneyi etkileyen işlem ayrıca tutarlılık gerektirir.

Küçük doğrulamalarla davranışı kontrol edin

Bu derste henüz bir test framework’ü öğrenmedik. Yine de beklenen davranışı birkaç assert ile görünür hâle getirebiliriz:

Buradaki assert ifadeleri pytest öğretimi değildir. Amaç, “kod çalıştı” demek yerine birkaç önemli beklentiyi açıkça doğrulamaktır. Nesne II’de aynı düşünceyi birim ve regresyon testleriyle sistematik hâle getireceğiz.

Gereksinim 1 — Üye başına en fazla üç kitap

Yeni kural:

Bir üye aynı anda en fazla üç kitap ödünç alabilir.

Bu kontrolü nereye koymalıyız?

Seçenekler:

  • Book.borrow_by()
  • Member.add_book()
  • Library.borrow()

Book başka hangi kitapların üyede olduğunu bilmez. Member, kendi ödünç kitap listesinin sahibidir. Bu nedenle sınır kuralını Member içinde korumak güçlü bir adaydır.

Ancak burada yeni bir tasarım problemi ortaya çıkar. Yalnızca add_book() içine kontrol eklersek Library.borrow() önce kitabı ödünçte işaretleyip sonra üyenin sınırına takılabilir. Bu durumda iki nesnenin durumu birbirini tutmaz.

Bu yüzden doğrulama ile durum değişimini ayırabiliriz:

class Member:
    MAX_BORROWED_BOOKS = 3

    def __init__(self, member_id, name):
        self.member_id = member_id
        self.name = name
        self.borrowed_books = []

    def ensure_can_borrow(self, book):
        if len(self.borrowed_books) >= self.MAX_BORROWED_BOOKS:
            raise ValueError("Ödünç kitap sınırı dolu")
        if book in self.borrowed_books:
            raise ValueError("Kitap üyede zaten kayıtlı")

    def add_book(self, book):
        self.ensure_can_borrow(book)
        self.borrowed_books.append(book)

Library.borrow() ise iki nesneyi değiştirmeden önce gerekli kontrolleri yaptırır:

def borrow(self, member_id, isbn):
    member = self.members.get(member_id)
    book = self.books.get(isbn)

    if member is None:
        raise ValueError("Üye bulunamadı")
    if book is None:
        raise ValueError("Kitap bulunamadı")

    member.ensure_can_borrow(book)
    if not book.is_available:
        raise ValueError("Kitap zaten ödünçte")

    book.borrow_by(member)
    member.add_book(book)

add_book() kendi kuralını yine koruyor; koordinasyonu yapan Library ise bütün ön koşulları durum değişikliklerinden önce kontrol ediyor.

ImportantBir sınıfın invariant’ı yetmeyebilir

Tek bir nesnenin kurallarını korumak önemlidir. Fakat bir işlem iki nesnenin durumunu birlikte değiştiriyorsa nesneler arası tutarlılığı da düşünmeliyiz. Başarısız bir işlem, sistemin yarısını değiştirmiş bırakmamalıdır.

Gereksinim 2 — İade işlemi

Şimdi Library.return_book(member_id, isbn) davranışı ekleyin.

İşlem başarılı olduğunda:

  • kitap yeniden kullanılabilir olmalı,
  • kitap üyenin borrowed_books listesinden çıkarılmalı.

İşlem başlamadan önce şu durumları düşünün:

  • üye yok,
  • kitap yok,
  • kitap zaten kütüphanede,
  • kitap başka bir üyede,
  • kitap üyeyi gösteriyor ama üyenin ödünç kitap listesinde bulunmuyor.

Bu son durum, iki nesnenin daha önceden tutarsız hâle gelmiş olabileceğini hatırlatır. Güncellemeden önce ilişkiyi doğrulamak gerekir. Book.borrowed_by özelliği ilişkinin okunmasına izin verir; gerçek değişiklik ise yine Book metoduyla yapılır.

ImportantÖnce doğrula, sonra iki nesneyi değiştir

Bir işlem birden fazla nesnenin durumunu değiştirecekse kontrolleri mümkün olduğunca önce yapın. Kitabı kullanılabilir yapıp daha sonra üye kontrolünde hata almak iki nesneyi tutarsız bırakabilir.

Alıştırma — Eksik davranışı tamamla

Aşağıdaki sadeleştirilmiş sürümde iade davranışını tamamlayın. Library, nesnelerin iç alanlarını doğrudan değiştirmek yerine onların davranışlarına delegasyon yapmalıdır.

book.return_book()
member.remove_book(book)

Bu çözümde Library koordinasyonu üstlenir; Book ve Member ise kendi durumlarını kendi metotları üzerinden değiştirir. Böylece 8. haftadaki kapsülleme ile 9. haftadaki delegation birlikte kullanılmış olur.

Tasarım inceleme — Gereksiz kalıtım mı?

Bir öğrenci şu tasarımı öneriyor:

class LibraryItem:
    ...

class Book(LibraryItem):
    ...

class Member(LibraryItem):
    ...

Sadece Book ve Member sınıflarında id benzeri alan olduğu için ortak üst sınıf oluşturmuş.

Şu soruları sorun:

  • Member is a LibraryItem cümlesi problem alanında anlamlı mı?
  • Bir LibraryItem beklenen yerde Member kullanmak üst türün sözleşmesini korur mu?
  • Ortak alan tek başına kalıtım için yeterli mi?
  • Kalıtım yerine bağımsız sınıflar daha açık olabilir mi?

Burada muhtemel karar kalıtımı reddetmektir.

Tasarım inceleme — Tür zinciri mi polimorfizm mi?

Aşağıdaki çıktı sistemini düşünün:

def export(report, data):
    if type(report) is TextReport:
        return report.render_text(data)
    elif type(report) is CompactReport:
        return report.render_compact(data)

İki sınıfa da render(data) davranışı verirsek bu fonksiyonu nasıl sadeleştirebiliriz? Bu, 11. haftanın polimorfizm ilkesini bütünleştirir.

Gözetimli mikro görev

Ders içi/final benzeri uygulamada başlangıç kodu öğretim elemanı tarafından verilebilir. Öğrenciden tek bir sınırlı değişiklik beklenir. Örnek gereksinimler:

  • üye başına kitap sınırı ekleme ve başarısız durumda iki nesnenin de değişmediğini doğrulama,
  • iade işlemi ekleme,
  • Book.__eq__ davranışını ISBN’e göre tanımlama,
  • find_book() metodunu Book | None sözleşmesiyle ekleme,
  • Book.__str__ temsilini iyileştirme,
  • yeni bir bildirim nesnesini polimorfik akışa ekleme.

Görev tamamlandıktan sonra en az üç önemli beklentiyi kısa assert ifadeleriyle doğrulayın. Örneğin başarılı işlem, reddedilen işlem ve nesneler arası tutarlılık için ayrı kontroller yazılabilir.

Değerlendirmede yalnızca sonuç değil şu kanıtlar da önemlidir:

  1. Değişiklik doğru sınıfa mı yerleştirildi?
  2. Nesne invariant’ı bozuluyor mu?
  3. Birden fazla nesne değişiyorsa başarısız işlem sistemi yarım güncellenmiş bırakıyor mu?
  4. Başka nesnenin iç durumuna doğrudan yazmak yerine uygun davranış/sorumluluk kullanıldı mı?
  5. Gereksiz kalıtım/sınıf/koşul zinciri eklendi mi?
  6. Beklenen davranış en az birkaç açık kontrolle doğrulandı mı?
  7. Öğrenci tasarım kararını kısa biçimde açıklayabiliyor mu?

Dönem özeti

Bu dersin geçiş sürümünde iki ayrı gelişim hattı tamamlandı.

İlk beş hafta Python’a geçişte:

  • temel sözdizimi,
  • kontrol akışı,
  • koleksiyonlar,
  • fonksiyonlar,
  • dosya ve istisnalar

hızlı biçimde kuruldu.

OOP çekirdeğinde ise:

  • nesne ve referans,
  • sınıf ve metot,
  • kapsülleme ve invariant,
  • kompozisyon ve delegation,
  • kalıtım,
  • polimorfizm,
  • özel metotlar,
  • özel istisnalar,
  • modül/paket ve tip ipuçları,
  • küçük davranış doğrulamaları

uygulandı.

Bu noktada temel yeterlik şu cümleyle özetlenebilir:

Bir problem alanındaki durumu ve davranışı uygun nesnelere dağıtabilmek, nesnelerin geçerli ve birbirleriyle tutarlı durumunu korumak ve küçük bir değişikliğin etkisini açıklayabilmek.

Nesne Tabanlı Programlama II dersinde bu temel, gerçek repository, Git, test, hata ayıklama, pull request ve denetlenebilir AI/agent kullanımıyla yazılım geliştirme sürecine taşınacaktır.

Kendinizi kontrol edin

  1. Yeni bir kuralı hangi sınıfa koyacağınıza nasıl karar verirsiniz?
  2. Bir nesnenin invariant’ı ne zaman kontrol edilmelidir?
  3. İki nesnenin durumunu birlikte değiştiren işlemde neden bütün ön koşulları önce kontrol etmek isteriz?
  4. Aynı nesne iki koleksiyonda bulunabilir mi?
  5. Koordinasyonu yapan sınıf neden başka nesnenin iç niteliğini doğrudan değiştirmek yerine o nesnenin metodunu çağırabilir?
  6. Kalıtım için hangi anlamsal ve davranışsal ilişkiyi ararsınız?
  7. Polimorfizm bir koşul zincirini nasıl azaltabilir?
  8. assert, __eq__, is, __str__ ve type hint farklı hangi amaçlara hizmet eder?

Son kontrol

Dönem sonunda aşağıdaki cümlelerin her biri için kendi kodunuzdan bir örnek gösterebilmelisiniz:

  • “Bu iki değişken aynı nesneyi gösteriyor.”
  • “Bu kural nesnenin invariant’ıdır.”
  • “Bu davranış bu sınıfın sorumluluğundadır.”
  • “Bu işlem iki nesneyi değiştirdiği için bütün kontrolleri değişiklikten önce yaptım.”
  • “Bu nesnenin iç alanını dışarıdan değiştirmek yerine davranışına delegasyon yaptım.”
  • “Burada kalıtım yerine kompozisyon daha uygundur.”
  • “Bu iki farklı nesne aynı metot sözleşmesiyle polimorfik kullanılabilir.”
  • “Bu özel istisna belirli bir alan hatasını ifade ediyor.”
  • “Bu assert ifadesi değişiklikten sonra beklenen davranışı doğruluyor.”
Back to top