Kapsülleme, property ve geçerli durum

Bir sınıf yazmak, nitelikleri dış dünyaya açıp birkaç metot eklemekten ibaret değildir. Nesnenin her zaman anlamlı bir durumda kalması gerekir. Bu hafta kapsülleme (encapsulation) fikrini, kontrollü durum değişimini ve değişmez koşul (invariant) kavramını kullanacağız.

Bu hafta neleri yapabilmelisiniz?

  • kapsüllemenin amacını “veriyi gizlemek”ten daha geniş biçimde açıklayabilmeli,
  • bir nesnenin invariant’ını belirleyebilmeli,
  • başlangıç durumunu __init__ içinde doğrulayabilmeli,
  • durum değişimlerini metotlarla sınırlandırabilmeli,
  • property ile kontrollü okuma/yazma arayüzü kurabilmeli,
  • geçersiz girişte nesneyi yarım değiştirilmiş durumda bırakmamalısınız.

Değişmez koşul (invariant) nedir?

Değişmez koşul (invariant), nesnenin geçerli kabul edildiği her anda doğru olması gereken kuraldır.

Bir banka hesabı için örnek kural:

balance >= 0

Bir ürün için:

price > 0 ve stock >= 0

Bir ders kaydı için:

0 <= score <= 100

Bu kurallar yalnızca veri doğrulama ayrıntısı değil, nesnenin ne anlama geldiğinin bir parçasıdır.

Geçersiz başlangıcı engellemek

Nesne ilk andan itibaren geçerli olmalıdır.

Kontrollü durum değişimi

_balance başındaki tek alt çizgi “bu niteliği sınıfın iç detayı olarak kabul et” biçiminde bir programcı sözleşmesidir. Python bunu teknik olarak tamamen erişilemez yapmaz.

Kapsüllenmiş banka hesabında geçerli durumun korunması ve bakiye değişiminin deposit ile withdraw metotları üzerinden yapılması.
ImportantKapsülleme = erişimi yasaklamak değildir

Amaç, nesnenin durumunun hangi kurallarla değişeceğini tek bir güvenilir arayüzde toplamaktır. Python’da kapsülleme çoğu zaman katı erişim engelinden çok sorumluluk ve arayüz disiplini ile sağlanır.

property ne zaman anlamlı?

Bir niteliği kullanıcıya account.balance biçiminde okunabilir sunmak ama değişimini kontrol altında tutmak isteyebiliriz.

@property
def balance(self):
    return self._balance

Bu durumda account.balance okunabilir; ancak setter tanımlamadığımız için normal account.balance = -100 ataması yapılamaz.

Bazı durumlarda setter anlamlıdır:

Setter, doğrudan atama sözdizimini korurken kuralı tek noktada uygular.

Fail fast

Geçersiz durum ortaya çıktığında sorunu mümkün olduğunca erken bildirmek genellikle daha güvenlidir:

Product("Klavye", -100)

oluşturulurken hata vermesi, negatif fiyatın sistemde dolaşıp çok daha sonra yanlış rapor üretmesinden iyidir.

Yarım güncelleme tehlikesi

Şu metodu düşünün:

def transfer_to(self, other, amount):
    self._balance -= amount
    if self._balance < 0:
        raise ValueError("Yetersiz bakiye")
    other._balance += amount

Kontrol değişiklikten sonra yapılmıştır. Hata üretildiğinde self._balance zaten negatif olmuş olabilir.

Daha güvenli sıra:

  1. tüm ön koşulları kontrol et,
  2. sonra durumu değiştir.

Bu ilkeyi dönem boyunca kullanacağız.

Alıştırma — Invariant belirle

Aşağıdaki sınıflar için en az iki invariant yazın:

  • Student
  • Product
  • Reservation

Örneğin tarihlerin sırası, puan aralığı, pozitif fiyat, negatif olmayan stok gibi kurallar düşünün.

Alıştırma — Güvenli withdraw

if amount <= 0:
    raise ValueError("Tutar pozitif olmalı")
if amount > self._balance:
    raise ValueError("Yetersiz bakiye")

Alıştırma — Property mi metot mu?

Aşağıdakileri tartışın:

  • account.balance
  • account.withdraw(100)
  • rectangle.area
  • order.cancel()

Hangileri “durumdan hesaplanan bir özellik” gibi, hangileri “eylem/komut” gibi okunuyor? Python’da her şeyi property yapmak da her şeyi get_...() metoduna dönüştürmek de gerekli değildir. Arayüzün niyetini açık kılın.

Küçük üretim görevi — Güvenli ürün

Product sınıfı yazın:

  • name: boş olamaz,
  • price: 0’dan büyük olmalı,
  • stock: negatif olamaz.

Metotlar:

  • add_stock(amount)
  • sell(amount)

sell yetersiz stokta hata vermeli ve stok değerini değiştirmemelidir. Fiyatı property üzerinden okunabilir yapın; setter ekleyip eklememeye tasarım gerekçesiyle karar verin.

Kendinizi kontrol edin

  1. Invariant nedir?
  2. Neden doğrulama yalnızca kullanıcı arayüzünde yapılmamalıdır?
  3. _balance Python’da gerçekten erişilemez midir?
  4. property hangi durumda kullanışlıdır?
  5. Neden önce doğrulayıp sonra durumu değiştirmek daha güvenlidir?

Bu haftadan akılda kalması gerekenler

  • Kapsülleme, nesnenin geçerli durumunu koruyan bir sorumluluk sınırıdır.
  • Invariant, nesnenin her geçerli durumda sağlaması gereken kuraldır.
  • Başlangıç ve durum geçişleri doğrulanmalıdır.
  • _name bir erişim yasağı değil, iç kullanım niyetidir.
  • property, arayüzü sade tutarken kontrol ekleyebilir.
Back to top