Gözetimli bütünleştirici nesne tasarımı
Son haftada yeni bir OOP kavramı yok. Dönem boyunca öğrendiklerinizi, kapsamı belli küçük bir değişiklikte birlikte kullanacaksınız: bir nesnenin o anki durumunu okumak, her kuralı doğru sınıfa koymak, nesnenin geçersiz bir duruma düşmesini önlemek, iki nesne arasındaki bağı tutarlı tutmak ve değişikliğin başka neleri etkilediğini açıklamak.
Bu bölümün kapsamı
- Bir sistemi okurken hangi sınıfın hangi durumu koruduğunu belirleme
- Birden çok nesneyi değiştiren işlemde nesneler arası tutarlılık
invelist.remove()işlemlerinin==eşitliğini kullanması__eq__eklemenin koleksiyon davranışını da değiştirmesi- İnvariant için
raise, geliştirici kontrolü içinassert - Bir iş kuralının hangi sınıfa ait olduğuna, kuralın koruduğu durumun sahibine bakarak karar verme
- Tasarım değişikliğini regresyon kontrolleriyle doğrulama
- Transaction (birkaç değişikliği ya hep birlikte ya hiç uygulayan düzenek) ve geri alma (rollback)
- Kalıcı veri saklama ve eşzamanlılık
pytest/unittestile otomatik test yazımı
Bu başlıklar konunun devamıdır; ileride karşınıza çıkar ama bu derste ezberlemeniz beklenmiyor.
Yeni gereksinim hangi sınıfın sorumluluğuna ait?
Aşağıda çalışan küçük bir kütüphane modeli var: Book, Member ve Library. Bu modele üç gereksinim ekleyeceğiz ve dönem boyunca verdiğimiz kararları aynı problemde kullanacağız. Yazacağınız kod kısa. Zorlanacağınız yer, her kuralı doğru sınıfa koymak ve bir kitap ödünç verildiğinde ya da iade edildiğinde kitabın ve üyenin kayıtlarını birbiriyle uyumlu bırakmak.
Başlangıç sistemi
Gerçek bir kütüphane sistemi verileri kalıcı olarak saklar ve aynı anda birden çok işlemi yürütür. O durumda birden fazla nesneyi değiştiren işlemler için transaction gibi daha güçlü düzenekler gerekebilir. Bu model bunları dışarıda bırakır. Amacı, önce doğrula → sonra değiştir ilkesini ve her sınıfın hangi işten sorumlu olduğunu açıkça göstermektir.
Önce sistemi okuyun
Kod eklemeden şu soruları cevaplayın:
Bookhangi durumu koruyor?Memberhangi koleksiyonu yönetiyor ve dışarıya neden listenin kendisini değil de bir tuple kopyasını veriyor?Libraryneden kitap ve üyeleri sözlükte tutuyor?Library.borrow()hangi işleri kendi yapıyor, hangilerini nesnelere devrediyor?- Aynı
Booknesnesi hemLibrary.booksiçinde hem üyenin koleksiyonunda bulunabilir mi? Bu iki ayrı kitap anlamına gelir mi? - Hangi kontroller iki nesne değişmeden önce yapılmalıdır?
Nesneler arası tutarlılık
Her nesnenin kendi invariant’ını (her zaman doğru kalması gereken koşulunu) koruması yetmeyebilir. Ödünç alma hem Book hem Member nesnesini değiştirir. Kitap ödünçte diye işaretlendikten sonra üyenin listesine ekleme başarısız olursa kitap ödünçte görünür ama hiçbir üyenin listesinde yer almaz. Sistem yarım güncellenmiş kalır.
Bu yüzden Library.borrow() şu sırayı izler:
- Üye ve kitap bulunur,
- Üyenin ödünç alma koşulları kontrol edilir,
- Kitabın rafta olup olmadığı kontrol edilir,
- Ancak bundan sonra iki nesne de değiştirilir.
Bir işlem birden fazla nesneyi birlikte değiştiriyorsa, bu nesnelerin birbiriyle uyumlu kalması (nesneler arası tutarlılık) ayrıca düşünülmesi gereken bir tasarım sorusudur.
in ve remove() hangi eşitliği kullanıyor?
Şu iki satıra dikkat edin:
if book in self._borrowed_books:
...
self._borrowed_books.remove(book)in ile listede eleman aramak ve list.remove() ile eleman silmek, eşleşen elemanı == ile bulur. Başlangıç sisteminde Book sınıfında __eq__ tanımlı değil. Bu yüzden ISBN’leri aynı olsa bile iki ayrı Book örneği eşit sayılmaz.
Şimdi 12. haftadaki gibi ISBN eşitliği ekleyelim:
class Book:
...
def __eq__(self, other):
if not isinstance(other, Book):
return NotImplemented
return self.isbn == other.isbnArtık:
first = Book("978-1", "Python")
second = Book("978-1", "Aynı ISBN, başka nesne")
print(first is second) # False
print(first == second) # TrueBu yüzden second in [first] de True döner.
__eq__ eklemek yalnızca a == b satırını değiştirmez
Eşitliği tanımladığınızda in, list.remove(), iki listenin == ile karşılaştırılması ve başka koleksiyon işlemleri de bu tanımı kullanır, davranışları değişebilir. Book.__eq__’yi ISBN’e göre tanımladığınız anda Member sınıfındaki book in self._borrowed_books kontrolü ve remove() çağrısı da, bu satırlara hiç dokunmadığınız hâlde farklı davranmaya başlar.
Bu her zaman kötü değildir; kütüphane modeliniz için istediğiniz davranış bu olabilir. Ama bu etkiyi bilerek test etmeniz gerekir.
Mikro deney: eşitliğin yan etkisini görün
Listede probe ile aynı nesne yok. Yine de remove(probe), ISBN’i aynı olduğu için eşit sayılan registered nesnesini listeden siler. Modelinizde bunun doğru olup olmadığı bir tasarım kararıdır; bu kararı bilerek verin.
assert ne için kullanılmalı?
Ders boyunca bir noktada neyin doğru olmasını beklediğimizi koda yazmak için şöyle satırlar kullanabiliriz:
assert book.borrowed_by is member
assert len(member.borrowed_books) == 1assert, koşul yanlışsa AssertionError verir. Bu tür kontroller test yazmaya doğru atılmış iyi bir ilk adımdır.
Ama program gerçek kullanıcılara hizmet verirken (üretimde) gelen girdileri assert ile doğrulamayın. Şu satır bir iş kuralını korumaya çalışıyor ve bu güvenli değil:
assert amount > 0, "Tutar pozitif olmalı"Python -O (optimize) seçeneğiyle çalıştırıldığında assert satırları kaldırılabilir; o zaman kural da ortadan kalkar.
raise, geliştirici kontrolü için assert
Kullanıcı girdisini, bir iş kuralını ya da nesnenin geçerli durumunu koruyan kontrol programın normal işleyişinin parçasıdır; bunun için if ...: raise ... kullanın. assert ise geliştirirken ya da test ederken “programcı olarak burada doğru olduğunu varsaydığım şey gerçekten doğru mu?” diye sorduğunuz kontroller içindir.
Gereksinim 1: Üye başına en fazla üç kitap
MAX_BORROWED_BOOKS = 3 kuralı için en uygun yer Member sınıfıdır, çünkü üyenin ödünç aldığı kitapların listesini o tutar. İşlemi yöneten Library de iki nesneyi değiştirmeden önce member.ensure_can_borrow(book) çağırır. Böylece sınır doluysa hiçbir nesne değişmeden işlem durur ve yarım güncelleme riski azalır.
Şunları test edin:
- 1., 2. ve 3. kitap başarıyla alınır,
- kitap reddedilir,
- Reddedilen kitap hâlâ ödünç alınabilir durumdadır,
- Üyenin koleksiyonunda yalnızca üç kitap vardır.
Gereksinim 2: İade işlemi
İade kodunu yazmadan önce şu durumları düşünün:
- Üye yok,
- Kitap yok,
- Kitap zaten kütüphanede,
- Kitap başka bir üyede,
- Kitabın
borrowed_byniteliği üyeyi gösteriyor ama kitap üyenin listesinde yok.
Başarılı bir iadeden sonra şu iki koşul doğru olmalı:
assert book.is_available
assert book not in member.borrowed_booksBu assertlar test kontrolüdür; return_book() içindeki iş kurallarının yerini tutmaz.
Gereksinim 3: ISBN’e göre eşitlik
Book sınıfına nesneleri ISBN’e göre karşılaştıran bir __eq__ ekleyin. Sonra aşağıdaki regresyon kontrollerini çalıştırın. Regresyon kontrolü, bir değişiklikten sonra eskiden çalışan davranışın bozulup bozulmadığını ya da nasıl değiştiğini sınar:
- Aynı ISBN’li iki farklı nesne
==ile eşit,isile farklı olmalı. - Aynı ISBN’li ikinci nesne, üyelik kontrolünde ilk nesneyle eşleşmeli.
remove()davranışının artık ISBN eşitliğinden etkilendiğini gösterin.- Bu davranışı kütüphane modelinizde isteyip istemediğinizi kısaca gerekçesiyle yazın.
Bu görevde 6. haftanın kimlik (is), 9. haftanın nesne koleksiyonları ve 12. haftanın eşitlik (==, __eq__) konularını birlikte kullanırsınız.
Gözetimli bütünleştirici görev
Başlangıç sistemine şu küçük değişiklikleri uygulayın:
- Üye başına en fazla üç kitap kuralını doğrulayın,
- Güvenli iade davranışını tamamlayın,
Book.__eq__’yi ISBN’e göre tanımlayın,Member’ın iç listesinin dışarıdan doğrudan değiştirilmesini engelleyin,- Başarılı ve başarısız işlemleri küçük kontrollerle doğrulayın.
Çalışır kod görevi. En az şu senaryoları otomatik olarak kontrol eden çalışır bir dosya hazırlayın:
- Normal ödünç alma,
- Aynı kitabı tekrar alma,
- Dördüncü kitap sınırı,
- Başarılı iade,
- Yanlış üyeden iade,
- Aynı ISBN’li farklı
Bookörnekleriyle eşitlik/üyelik davranışı.
İş kurallarını assert ile değil raise ile koruyun; assertları yalnızca testlerde sonucu doğrulamak için kullanın.
Kısa tasarım savunması
Kodun altına şu sorular için en fazla 150 kelimelik bir cevap yazın:
- Ödünç alma sınırı neden
Membersınıfının sorumluluğudur? Libraryön koşulları neden yine de değişiklikten önce sırayla kontrol eder?Book.__eq__eklemek koleksiyon davranışını nasıl değiştirdi?Member’ın iç listesi neden dışarıya değiştirilebilir (mutable) bir liste olarak verilmedi?- Hangi kontrolleri
raise, hangileriniassertile yazdınız ve neden?
Dönem özeti
- Nesne tasarımında sınıfları yazmak işin bir kısmıdır. Hangi nesnenin durumunu kimin ve hangi sırayla değiştireceğine de siz karar verirsiniz.
- Birden fazla nesneyi değiştiren işlemde nesneler arası tutarlılık ayrıca düşünülmelidir.
__eq__tanımıinveremove()gibi koleksiyon işlemlerini de etkiler.isiki adın aynı nesneyi gösterip göstermediğini,==ise iki nesnenin eşit sayılıp sayılmadığını sorar.assertgeliştirirken ve test ederken kullanılır;python -Oile kaldırılabildiği için iş kuralını korumaz.- Kapsülleme, nesnenin içindeki değiştirilebilir (mutable) listeleri dışarıdan korumayı da gerektirir.
- Dönem sonunda sizden beklenen, küçük bir değişikliği gerekçesini açıklayarak yapmanız, kodun çalıştığını göstermeniz ve kontrollerle doğrulamanızdır.
Bu bölümün kazanımları
Bu bölümü bitiren öğrenci:
- Bir sistemi okurken hangi sınıfın hangi durumu koruduğunu belirler.
- Birden çok nesneyi değiştiren işlemde nesneler arası tutarlılığı gözetir.
invelist.remove()işlemlerinin==eşitliğini kullandığını gösterir.__eq__eklemenin koleksiyon davranışını da değiştirdiğini fark eder ve etkiyi sınar.- İnvariant’ı
raiseile, geliştirici kontrolünüassertile yazar. - Bir iş kuralını, koruduğu durumun sahibi olan sınıfa koyar.
- Bir tasarım değişikliğini regresyon kontrolleriyle doğrular.