4 - Kapsülleme, Soyutlama ve Erişim Belirleyiciler
2026
Bu hafta aslında tek bir probleme odaklanıyoruz:
Bir nesnenin içindeki veriler nasıl “geçerli” kalır ve dışarıdan yanlış kullanımı nasıl azaltırız?
Örnek bir kural (invariant):
Bu tür kurallar, nesneyi “doğru” durumda tutar.
Aşağıdaki tasarımda, dışarıdan doğrudan erişim yüzünden kural kolayca bozulur:
class BankaHesabi:
def __init__(self, bakiye):
self.bakiye = bakiye # herkese açık
hesap = BankaHesabi(1000)
hesap.bakiye = -500 # kural bozuldu (bakiye negatif oldu)Bu hafta, bu tür durumları tasarım düzeyinde nasıl önleyeceğimizi konuşacağız.
Bu dersin sonunda şunları yapabiliyor olmanızı hedefliyoruz:
@property ile kontrollü erişim tasarlamak (doğrulama / salt okunur nitelik)Kapsülleme bir hedef: “Niteliklere rastgele dokunulmasın, kontrollü bir arayüz olsun.”
Bu hedefe yaklaşmak için iki araç kullanırız:
_ ve __ ile “iç detay” sinyali vermek (Python’da çoğu zaman kural değil, mesajdır.)@property: nitelik gibi görünen bir erişimin arkasına kontrol koymakBirazdan kapsüllemeyi anlattıktan sonra, bu iki aracı neden ve nasıl kullandığımız netleşecek.
Bu hafta üç başlık birbirine bağlanarak ilerleyecek:
Bu kavramlar; kodun bakımını, genişletilmesini ve yanlış kullanıma karşı dayanıklılığını artırır.
Bu hafta şunu netleştireceğiz:
“Dışarıdan bir nesneye hangi yollarla dokunulmalı?”
Ders boyunca iki şeye odaklanacağız:
Kapsülleme; nitelikleri (durum/state) ile bu nitelikleri işleyen metotları (davranış/behavior) tek bir birimde (sınıfta) toplama yaklaşımıdır. Amaç, sınıfın dışından bakınca:
Bir başka ifadeyle, kapsülleme “gizlemek”ten çok kontrollü kullanım tasarlamaktır.
Kısa örnek mantığı
“Yaş negatif olamaz” kuralı varsa, bunu her yerde değil tek yerde (setter / property) denetlemek isteriz.
Kapsülleme fikrini Python’da genelde şu iki yolla somutlaştırırız:
Erişim konvansiyonları (_ / __) İç detayları “dışarıdan kullanma” mesajı verir.
@property Nitelik gibi görünen bir kullanımın arkasına kontrol koyar (doğrulama, salt okunur, vb.).
Şimdi önce erişim konvansiyonlarını netleştirelim.
Erişim belirleyiciler (access modifiers), bir sınıfın niteliklerine ve metotlarına nerelerden erişilebileceğini belirleyen kurallardır. Amaç, sınıfın “iç detaylarını” rastgele kullanımı azaltmak ve dışarıya kontrollü bir arayüz sunmaktır.
Kısaca: “Bu nitelik/metoda kimler erişebilir?” sorusunun cevabıdır.
C++/Java/C# gibi dillerde public/protected/private, dilin zorunlu kıldığı erişim kurallarıdır.
Python’da ise:
public/protected çoğunlukla konvansiyon (isimlendirme) düzeyindedir.__ (çift alt çizgi) “tam gizlilik” sağlamaz; ad dönüştürme (name mangling) yapar.Kritik nokta
Python’daki bu mekanizmalar güvenlik (security) hedefiyle tasarlanmamıştır. Amaç daha çok “yanlışlıkla kullanımın” azalması ve tasarımın daha okunaklı olmasıdır.
Public: Normal isimler
Örn: ad, kaydet()
Protected (konvansiyon): Tek alt çizgi _
Örn: _kredi, _hesapla() Python erişimi engellemez; “bu üye iç kullanım içindir” mesajı verir.
Private benzeri (name mangling): Çift alt çizgi __
Örn: __bakiye Ad dönüştürme nedeniyle dışarıdan aynı adla erişim zorlaşır.
Aşağıdaki örnekte nitelikler ve metotlar doğrudan dışarıya açıktır.
class Ogrenci:
def __init__(self, ad, soyad, okul_no):
self.ad = ad
self.soyad = soyad
self.okul_no = okul_no
def bilgileri_goster(self):
print(f"Ad: {self.ad}, Soyad: {self.soyad}, Okul No: {self.okul_no}")
ogrenci1 = Ogrenci("Ayşe", "Yılmaz", "12345")
print(ogrenci1.ad)
ogrenci1.bilgileri_goster()Ne gördük?
_kredi niteliği, dışarıdan erişilebilir; fakat “bu üye API garantisi değildir” mesajı taşır.
class Ders:
def __init__(self, ders_adi, kredi):
self.ders_adi = ders_adi # public
self._kredi = kredi # protected (konvansiyon)
def ders_bilgisi_goster(self):
print(f"{self.ders_adi} ({self._kredi} kredi)")
class MuhendislikDersi(Ders):
def __init__(self, ders_adi, kredi, zorluk):
super().__init__(ders_adi, kredi)
self.zorluk = zorluk
def detayli_bilgi_goster(self):
print(f"{self.ders_adi} ({self._kredi} kredi) - Zorluk: {self.zorluk}")
ders1 = Ders("Matematik", 4)
print(ders1._kredi) # mümkün ama dış kod için kırılgan bir bağımlılık
muh_ders1 = MuhendislikDersi("Fizik", 3, "Orta")
muh_ders1.detayli_bilgi_goster()Neden “dışarıdan kullanma” diyoruz?
Bugün _kredi var; yarın sınıf tasarımında alan adı değişebilir veya tamamen kaldırılabilir. Dış kod _krediye bağlandıysa, küçük bir iç değişiklik dışarıyı da kırar.
Ne gördük?
_ erişimi engellemez; “iç detay” sinyalidir.Çift alt çizgi ile başlayan bazı isimler ad dönüştürmeye uğrar.
Örnek: BankaHesabi içinde __bakiye niteliği tanımlanırsa, Python bunu şu forma dönüştürür:
__bakiye → _BankaHesabi__bakiyeBu yüzden hesap.__bakiye yazdığınızda genellikle AttributeError görürsünüz: Nitelik aslında vardır, ama adı dönüştürülmüştür.
Not: Name mangling hangi isimlerde olur?
Genel kural: Başında en az iki alt çizgi olup sonunda __ ile bitmeyen isimlerde (örn. __bakiye) devreye girer. __init__, __str__ gibi özel metot adları bu amaçla kullanılmaz.
class BankaHesabi:
def __init__(self, hesap_no, ad_soyad, bakiye):
self.hesap_no = hesap_no
self.ad_soyad = ad_soyad
self.__bakiye = bakiye # name mangling
def para_yatir(self, miktar):
if miktar <= 0:
raise ValueError("Miktar pozitif olmalı.")
self.__bakiye += miktar
def para_cek(self, miktar):
if miktar <= 0:
raise ValueError("Miktar pozitif olmalı.")
if miktar > self.__bakiye:
raise ValueError("Yetersiz bakiye.")
self.__bakiye -= miktar
def bakiye_goruntule(self):
return self.__bakiye
hesap1 = BankaHesabi("TR123", "Ahmet Demir", 1000)
hesap1.para_yatir(500)
hesap1.para_cek(200)
print(hesap1.bakiye_goruntule())
# Mekanizmayı göstermek içindir; normal tasarımda kullanılmaz:
# print(hesap1._BankaHesabi__bakiye)Ne zaman __ kullanılır?
Doğrudan bir niteliğin değerine erişim kolaydır; fakat şu ihtiyaçlar doğar:
Bu ihtiyaçlar, getter/setter veya @property ile temiz biçimde çözülebilir.
class Sicaklik:
def __init__(self, celsius):
self._celsius = None
self.set_celsius(celsius)
def get_celsius(self):
return self._celsius
def set_celsius(self, celsius):
if celsius < -273.15:
raise ValueError("Mutlak sıfırın altında sıcaklık olamaz.")
self._celsius = celsius
def get_fahrenheit(self):
return (self._celsius * 9/5) + 32
s1 = Sicaklik(25)
print(s1.get_celsius())
print(s1.get_fahrenheit())
s1.set_celsius(30)
print(s1.get_celsius())Ne gördük?
get_..., set_...@property ile Pythonik Yaklaşım@property ile bir metodu nitelik gibi kullanırsınız.
Yani dışarıdan obj.celsius diye okunur / obj.celsius = 30 diye atanır; ama okuma/atama sırasında sizin yazdığınız getter/setter kodu çalışır (doğrulama, dönüşüm, log gibi).
Note
Setter yazmazsanız property salt okunur olur (dışarıdan değiştirilemez).
__init__ İçinde Doğrulamayı AtlamaBazı öğrenciler __init__ içinde doğrudan iç niteliğe yazar:
self._celsius = celsiusBu durumda setter’daki doğrulama devreye girmez.
Doğru yaklaşım
__init__ içinde de setter’dan geçmesini istiyorsanız: self.celsius = celsius yazmalısınız.
@property Örneğiclass SicaklikProperty:
def __init__(self, celsius):
self._celsius = None
self.celsius = celsius # setter çalışsın
@property
def celsius(self):
return self._celsius
@celsius.setter
def celsius(self, value):
if value < -273.15:
raise ValueError("Mutlak sıfırın altında sıcaklık olamaz.")
self._celsius = value
@property
def fahrenheit(self):
return (self._celsius * 9/5) + 32
s2 = SicaklikProperty(25)
print(s2.celsius)
print(s2.fahrenheit)
s2.celsius = 30
print(s2.celsius)
# SicaklikProperty(-500) # ValueErrorNe gördük?
obj.celsius = ... nitelik ataması gibi görünüyor.Soyutlama; karmaşık ayrıntıları geri plana itip, “nesne ne yapar?” sorusuna odaklanır.
Örnek: Bir aracı kullanmak için motor ayrıntılarını bilmeniz gerekmez; sizin için önemli olan “ilerle/dur/sağa dön” gibi işlevlerdir.
Yazılımda da benzer biçimde; sınıfların iç detaylarını geri plana itip “sözleşme” tanımlamak isteriz.
Pratik hedef
Bir soyut sınıf, “alt sınıflar şu metotları mutlaka sağlayacak” garantisini vermek için kullanılır.
abc Modülü ile Abstract Base Classfrom abc import ABC, abstractmethod
class Sekil(ABC):
@abstractmethod
def alan_hesapla(self):
pass
@abstractmethod
def cevre_hesapla(self):
passNe gördük?
Sekil, “şekil gibi davranan” tüm sınıflar için bir sözleşme oldu.import math
class Dikdortgen(Sekil):
def __init__(self, uzunluk, genislik):
self.uzunluk = uzunluk
self.genislik = genislik
def alan_hesapla(self):
return self.uzunluk * self.genislik
def cevre_hesapla(self):
return 2 * (self.uzunluk + self.genislik)
class Daire(Sekil):
def __init__(self, yaricap):
self.yaricap = yaricap
def alan_hesapla(self):
return math.pi * (self.yaricap ** 2)
def cevre_hesapla(self):
return 2 * math.pi * self.yaricapNe gördük?
math.pi kullanımı “sabitleri elle yazma” alışkanlığını azaltır.Sekil sözleşmesini sağlıyor.Aynı sözleşmeyi sağlayan farklı nesneler, tek bir listede ortak biçimde kullanılabilir.
sekiller = [Dikdortgen(5, 10), Daire(3), Dikdortgen(2, 7)]
for s in sekiller:
print(type(s).__name__, "Alan:", s.alan_hesapla(), "Çevre:", s.cevre_hesapla())Bu fikri haftaya Çok Biçimlilik (Polimorfizm) başlığında daha sistematik işleyeceğiz.
Bu iki kavram birbiriyle ilişkilidir ama aynı şey değildir:
Pratik ayrım: Kapsülleme nasıl sakladığınızdır, soyutlama neyi gösterdiğinizdir.
Bu alıştırmada iki hedef var:
para_yatir / para_cek).Bu alıştırmada get_... / set_... yerine, Pythonik biçimde @property kullanacağız.
Bu alıştırmada değerlendirme kolaylığı için mesajları aşağıdaki standartta üretin:
miktar <= 0 ise: "Geçersiz miktar""Yetersiz bakiye""Yetersiz bakiye (ceza dahil)"Format notu
Ceza/ faiz gibi ondalıklı değerleri iki basamak ile yazdırın: :.2f Örn: 10.00 TL, 123.45 TL
BankaHesabi(ABC) sınıfını tanımlayın:
Private nitelikler:
__hesap_no, __isim, __bakiyeSalt okunur property’ler:
hesap_no, isim, bakiyeKorumalı (iç kullanım) güncelleme metotları:
_bakiye_ekle(miktar)_bakiye_cikar(miktar)Soyut metotlar:
para_yatir(miktar) → işlem sonucu bilgisini string olarak döndürsünpara_cek(miktar) → işlem sonucu bilgisini string olarak döndürsünOrtak metot:
hesap_bilgisi_goster() → hesap no, isim, bakiye bilgisi döndürsünNeden bakiye salt okunur?
Dışarıdan hesap.bakiye = ... gibi bir atama yapılmasın istiyoruz. Bakiye güncellemesi sadece para_yatir/para_cek gibi kurallı metotlarla olmalı.
VadesizHesap(BankaHesabi)para_yatir(miktar):
miktar > 0 ise bakiyeye ekle; "500 TL yatırıldı. Güncel bakiye: 1500 TL" gibi bir mesaj döndür."Geçersiz miktar"para_cek(miktar):
miktar > 0 ve miktar <= bakiye ise bakiyeden düş; "200 TL çekildi. Güncel bakiye: 1300 TL" gibi bir mesaj döndür."Yetersiz bakiye""Geçersiz miktar"VadeliHesap(BankaHesabi)Ek private nitelikler: * __vade_suresi, __faiz_orani
Ek salt okunur property’ler: * vade_suresi, faiz_orani
Ek metot: * faiz_hesapla() → yıllık faiz tutarını hesaplayıp mesaj döndürsün (iki basamak)
Para çekme kuralı (erken çekim cezası):
miktar + ceza"500 TL çekildi (10.00 TL ceza uygulandı). Güncel bakiye: 4490 TL"Yetersizlik durumları:
miktar <= 0 ise "Geçersiz miktar"miktar + ceza bakiyeyi aşıyorsa "Yetersiz bakiye (ceza dahil)"from abc import ABC, abstractmethod
class BankaHesabi(ABC):
def __init__(self, hesap_no, isim, bakiye=0):
self.__hesap_no = hesap_no
self.__isim = isim
self.__bakiye = bakiye
@property
def hesap_no(self):
return self.__hesap_no
@property
def isim(self):
return self.__isim
@property
def bakiye(self):
return self.__bakiye # salt okunur # Alt sınıflar için kontrollü güncelleme
def _bakiye_ekle(self, miktar):
self.__bakiye += miktar
def _bakiye_cikar(self, miktar):
self.__bakiye -= miktar
@abstractmethod
def para_yatir(self, miktar):
pass # str döndürmeli
@abstractmethod
def para_cek(self, miktar):
pass # str döndürmeli
def hesap_bilgisi_goster(self):
return f"Hesap No: {self.hesap_no}, İsim: {self.isim}, Bakiye: {self.bakiye} TL"class VadeliHesap(BankaHesabi):
def __init__(self, hesap_no, isim, bakiye=0, vade_suresi=12, faiz_orani=0.05):
super().__init__(hesap_no, isim, bakiye)
self.__vade_suresi = vade_suresi
self.__faiz_orani = faiz_orani
@property
def vade_suresi(self):
return self.__vade_suresi
@property
def faiz_orani(self):
return self.__faiz_orani
def para_yatir(self, miktar):
# TODO: miktar kontrolü + _bakiye_ekle + mesaj döndür
pass
def para_cek(self, miktar):
# TODO: miktar kontrolü + %2 ceza + ceza dahil yeterlilik kontrolü + _bakiye_cikar + mesaj döndür
pass
def faiz_hesapla(self):
# TODO: yıllık faiz tutarı hesapla, :.2f formatıyla mesaj döndür
passAşağıdaki kod çalıştığında, metotların doğru davrandığını görebilmelisiniz.
if __name__ == "__main__":
vadesiz = VadesizHesap("123456", "Ahmet Yılmaz", 1000)
vadeli = VadeliHesap("789012", "Mehmet Kaya", 5000, 24, 0.08)
print(vadesiz.hesap_bilgisi_goster())
print(vadesiz.para_yatir(500)) # "500 TL yatırıldı. Güncel bakiye: 1500 TL"
print(vadesiz.para_cek(200)) # "200 TL çekildi. Güncel bakiye: 1300 TL"
print(vadesiz.para_cek(50000)) # "Yetersiz bakiye"
print(vadesiz.para_yatir(-5)) # "Geçersiz miktar"
print("\n" + vadeli.hesap_bilgisi_goster())
print(vadeli.para_yatir(1000)) # "1000 TL yatırıldı. Güncel bakiye: 6000 TL"
print(vadeli.para_cek(500)) # "500 TL çekildi (10.00 TL ceza uygulandı). Güncel bakiye: 5490 TL"
print(vadeli.para_cek(999999)) # "Yetersiz bakiye (ceza dahil)"
print(vadeli.faiz_hesapla())Ne ölçüyoruz?
bakiye dışarıdan set edilebiliyor mu? (set edilememeli)Bu örnekte amaç, farklı oynatıcıların aynı sözleşmeyi sağlayabilmesidir.
Senaryo: Uygulama bir “oynatıcı” ile çalışıyor. Oynatıcı ister video ister ses oynatsın, uygulama şu iki işi aynı şekilde çağırabilmeli:
play()pause()Buradaki fikir: uygulama “hangi tür oynatıcı” olduğunu bilmeden, sözleşmeye güvenerek çalışır.
Player(ABC) soyut sınıfı:
play() ve pause() metotları soyut olsun.VideoPlayer(Player):
play() → "Video oynatılıyor."pause() → "Video duraklatıldı."AudioPlayer(Player):
play() → "Ses oynatılıyor."pause() → "Ses duraklatıldı."music_player = AudioPlayer()
video_player = VideoPlayer()
music_player.play()
music_player.pause()
video_player.play()
video_player.pause()Neyi gösteriyor?
Aynı metot adlarıyla farklı nesnelerin çalışması, haftaya işleyeceğimiz Çok Biçimlilik (Polimorfizm) fikrine doğrudan zemin hazırlar.
Kapsülleme: Nitelik + metodu bir sınıfta toplamak; kontrollü API sunmak.
Python’da erişim:
_ (konvansiyon, engellemez)__ (name mangling, tam gizlilik değil)@property: Nitelik gibi kullanım + içeride kontrol (doğrulama/dönüşüm/log)
Sık hata: __init__ içinde doğrudan iç niteliğe atayıp setter doğrulamasını atlamak
Soyut sınıf (ABC): Alt sınıflar için sözleşme; metotlar sağlanmadan örneklenemez
Polimorfizm köprüsü: Aynı sözleşmeye sahip farklı nesneler ortak biçimde kullanılabilir
Mini kontrol soruları
__bakiye niteliğine obj.__bakiye ile neden erişemezsiniz? (Name mangling)_kredi dışarıdan okunabiliyorsa neden “dışarıdan kullanma” denir? (API garantisi değil)@property ile manuel getter/setter farkı nedir? (kullanım biçimi + kontrolün saklanması)Bu hafta, “sınıfın içi” ile “sınıfın dışarıya sunduğu arayüz” ayrımını netleştirdik:
Çok Biçimlilik (Polimorfizm) ile bu sözleşme fikrini daha sistematik biçimde kullanacağız.