Nesne Tabanlı Programlama 2

4 - Kapsülleme, Soyutlama ve Erişim Belirleyiciler

Emre Can Yılmaz

Ondokuz Mayıs Üniversitesi

2025

Giriş

Bu hafta, nesne tabanlı programlamanın temel prensiplerinden olan kapsülleme ve soyutlama kavramlarını inceleyeceğiz.

Ayrıca bu kavramlarla yakından ilişkili olan erişim belirleyicileri konusuna da değineceğiz.

Bu kavramlar, daha düzenli, güvenli ve sürdürülebilir yazılımlar geliştirmemize yardımcı olur.

Kapsülleme İlkesi

Kapsülleme Nedir?

Kapsülleme, veri ve bu veriyi işleyen metotları bir araya getirme ve bir birim (sınıf) içinde saklama prensibidir. Başka bir deyişle, sınıfımızın içindeki detayları dış dünyadan saklarız.

Bir kapsül gibi düşünün: İçinde değerli eşyalar var ve dış etkenlerden korunuyor. Sadece belirli yollarla (metotlarla) bu eşyalara erişebilir veya onları değiştirebilirsiniz.

Neden Kapsülleme?

  • Veri Güvenliği: Verilere doğrudan erişimi kısıtlayarak, yanlışlıkla veya kötü niyetli değişikliklerin önüne geçeriz.
  • Modülerlik: Kodumuz daha modüler ve anlaşılır hale gelir. Sınıflar kendi iç işleyişlerinden bağımsız çalışabilir.
  • Kodun Bakımı ve Değişimi Kolaylaşır: Sınıfın iç yapısını değiştirdiğimizde, dışarıdaki kodun etkilenme olasılığı azalır (eğer arayüzü yani public metotları değiştirmediysek).

Erişim Belirleyiciler: Public, Protected, Private

Erişim Belirleyiciler Ne İşe Yarar?

Erişim belirleyiciler, sınıfımızın üyelerine (özellikler ve metotlar) nereden erişilebileceğini kontrol etmemizi sağlar. Temelde üç ana erişim belirleyici vardır:

  • Public (Herkese Açık): Sınıf içinden, sınıf dışından, hatta alt sınıflardan bile erişilebilir. Her yerden erişime açık.
  • Protected (Korumalı): Sadece sınıf içinden ve alt sınıflardan erişilebilir. Sınıf dışından doğrudan erişilemez.
  • Private (Gizli): Sadece sınıf içinden erişilebilir. Sınıf dışından ve alt sınıflardan doğrudan erişilemez.

Python’da Erişim Belirleyiciler

Python’da diğer bazı dillerdeki gibi public, protected, private anahtar kelimeleri yoktur. Ancak, isimlendirme kuralları ile bu kavramları simüle ederiz:

  • Public (Herkese Açık): Normal isimler kullanılır. Örneğin: ortalama_not, ogrenci_adi, kaydet()
  • Protected (Korumalı): İsimlerin başına tek alt çizgi _ konulur. Örneğin: _sinif_listesi, _hesapla()
    • Uyarı: Python, protected üyelere sınıf dışından erişimi engellemez. Ancak, geliştiriciler bu kurala uyarak “bu üye sınıfın iç işleyişi için tasarlanmıştır, dışarıdan doğrudan değiştirmeyin” mesajını verirler.
  • Private (Gizli): İsimlerin başına çift alt çizgi __ konulur. Örneğin: __bakiye, __parola_kriptolu
    • Python, private üyelere sınıf dışından doğrudan erişimi zorlaştırır (isim karmaşası (name mangling) mekanizması ile). Tamamen engellemez, ama erişim kasıtlı ve daha karmaşık hale gelir.

Public Üye Örneği

class Ogrenci:
    def __init__(self, ad, soyad, okul_no):
        self.ad = ad  # public
        self.soyad = soyad # public
        self.okul_no = okul_no # public

    def bilgileri_goster(self): # public metot
        print(f"Ad: {self.ad}, Soyad: {self.soyad}, Okul No: {self.okul_no}")

ogrenci1 = Ogrenci("Ayşe", "Yılmaz", "12345")
print(ogrenci1.ad) # Public üyeye doğrudan erişim
ogrenci1.bilgileri_goster() # Public metoda erişim

Protected Üye Örneği

class Ders:
    def __init__(self, ders_adi, kredi):
        self.ders_adi = ders_adi # public
        self._kredi = kredi  # protected

    def ders_bilgisi_goster(self): # public metot
        print(f"Ders Adı: {self.ders_adi}, Kredi: {self._kredi}")

class MuhendislikDersi(Ders): # Alt sınıf
    def __init__(self, ders_adi, kredi, zorluk_seviyesi):
        super().__init__(ders_adi, kredi)
        self.zorluk_seviyesi = zorluk_seviyesi

    def detayli_bilgi_goster(self):
        print(f"Ders Adı: {self.ders_adi}, Kredi: {self._kredi}, Zorluk: {self.zorluk_seviyesi}") # Protected üyeye alt sınıftan erişim

ders1 = Ders("Matematik", 4)
print(ders1.ders_adi) # Public üyeye erişim
print(ders1._kredi) # Protected üyeye sınıf dışından erişim (UYARI: Python engellemez ama iyi bir pratik değil!)

muh_ders1 = MuhendislikDersi("Fizik", 3, "Orta")
muh_ders1.detayli_bilgi_goster() # Alt sınıftan protected üyeye erişim

Private Üye Örneği

class BankaHesabi:
    def __init__(self, hesap_no, ad_soyad, bakiye):
        self.hesap_no = hesap_no # public
        self.ad_soyad = ad_soyad # public
        self.__bakiye = bakiye # private

    def para_yatir(self, miktar): # public metot
        if miktar > 0:
            self.__bakiye += miktar
            print(f"{miktar} TL yatırıldı. Yeni bakiye: {self.__bakiye} TL")
        else:
            print("Geçersiz miktar.")

    def para_cek(self, miktar): # public metot
        if miktar > 0 and miktar <= self.__bakiye:
            self.__bakiye -= miktar
            print(f"{miktar} TL çekildi. Yeni bakiye: {self.__bakiye} TL")
        else:
            print("Geçersiz miktar veya yetersiz bakiye.")

    def bakiye_goruntule(self): # public metot
        print(f"Hesap Bakiyesi: {self.__bakiye} TL")
hesap1 = BankaHesabi("TR123", "Ahmet Demir", 1000)
hesap1.para_yatir(500)
hesap1.para_cek(200)
hesap1.bakiye_goruntule()

# print(hesap1.__bakiye) # Hata! Private üyeye sınıf dışından doğrudan erişim engellenir.
print(hesap1._BankaHesabi__bakiye) # İsim karmaşası (Name mangling) ile private üyeye dolaylı erişim (Genellikle kaçınılması gereken bir durum)

Özet: Python’da erişim belirleyiciler isimlendirme kuralları ile sağlanır. _ ve __ kullanarak verilerimizi kapsülleyebilir ve daha kontrollü erişim sağlayabiliriz.

Getter ve Setter Metotları

Neden Getter ve Setter?

Doğrudan özelliklere erişimi kısıtladığımızda (özellikle private veya protected yaptığımızda), verilere erişmek ve onları değiştirmek için kontrollü yollar oluşturmamız gerekir. İşte burada getter (erişici) ve setter (değiştirici) metotları devreye girer.

  • Getter Metotları (Erişiciler): Bir özelliğin değerini okumak için kullanılır. Genellikle get_ozellik_adi() veya sadece ozellik_adi() şeklinde isimlendirilir.
  • Setter Metotları (Değiştiriciler): Bir özelliğin değerini değiştirmek için kullanılır. Genellikle set_ozellik_adi(yeni_deger) veya ozellik_adi = yeni_deger (property ile) şeklinde isimlendirilir.

Getter ve Setter’ın Avantajları

  • Veri Doğrulama (Validation): Setter metotları içinde, yeni değerin geçerli olup olmadığını kontrol edebiliriz. Örneğin, yaş değerinin negatif olmasını engelleyebiliriz.
  • Veri Dönüşümü/İşleme: Getter metotları, veriyi döndürmeden önce üzerinde işlem yapabilir. Örneğin, bir sıcaklık değerini Celcius’tan Fahrenheit’a dönüştürebiliriz.
  • Salt Okunur (Read-Only) Özellikler: Sadece getter metodu tanımlayarak, özelliğin sadece okunabilir olmasını sağlayabiliriz. Setter metodu tanımlamazsak, özellik dışarıdan değiştirilemez.
  • Yazmaç Günlüğü (Logging): Setter metotları içinde, özelliğin her değiştirildiğinde bir log kaydı tutabiliriz.

Getter ve Setter Örneği (Python)

Manuel Getter ve Setter

class Sicaklik:
    def __init__(self, celcius):
        self.__celcius = celcius # private

    def get_celcius(self): # Getter metodu
        return self.__celcius

    def set_celcius(self, celcius): # Setter metodu
        if celcius < -273.15: # Mutlak sıfır kontrolü (veri doğrulama)
            print("Geçersiz sıcaklık değeri! Mutlak sıfırın altında olamaz.")
        else:
            self.__celcius = celcius

    def get_fahrenheit(self): # Getter metodu (dönüşümlü veri)
        return (self.__celcius * 9/5) + 32
sicaklik1 = Sicaklik(25)
print(f"Celcius: {sicaklik1.get_celcius()}") # Getter ile okuma
print(f"Fahrenheit: {sicaklik1.get_fahrenheit()}") # Getter ile dönüştürülmüş veri okuma

sicaklik1.set_celcius(30) # Setter ile değiştirme
print(f"Yeni Celcius: {sicaklik1.get_celcius()}")

sicaklik1.set_celcius(-300) # Geçersiz değer kontrolü
print(f"Celcius (geçersiz denemeden sonra): {sicaklik1.get_celcius()}") # Değer değişmedi

@property Dekoratörü ile Getter ve Setter

Python’da getter ve setter metotlarını daha şık ve Pythonik bir şekilde tanımlamak için @property dekoratörünü kullanabiliriz. Bu dekoratör, metotları özellik gibi kullanmamızı sağlar.

Örnek

class SicaklikProperty:
    def __init__(self, celcius):
        self.__celcius = celcius

    @property
    def celcius(self): # Getter metodu (özellik gibi erişilir)
        return self.__celcius

    @celcius.setter
    def celcius(self, celcius): # Setter metodu (özellik gibi atanır)
        if celcius < -273.15:
            print("Geçersiz sıcaklık değeri! Mutlak sıfırın altında olamaz.")
        else:
            self.__celcius = celcius

    @property
    def fahrenheit(self): # Sadece getter (salt okunur özellik)
        return (self.__celcius * 9/5) + 32
sicaklik2 = SicaklikProperty(25)
print(f"Celcius: {sicaklik2.celcius}") # Özellik gibi erişim (getter)
print(f"Fahrenheit: {sicaklik2.fahrenheit}") # Salt okunur özellik erişimi

sicaklik2.celcius = 30 # Özellik gibi atama (setter)
print(f"Yeni Celcius: {sicaklik2.celcius}")

sicaklik2.celcius = -300 # Geçersiz değer kontrolü
print(f"Celcius (geçersiz denemeden sonra): {sicaklik2.celcius}")

# sicaklik2.fahrenheit = 100 # Hata! Salt okunur özellik, setter'ı yok.

Özet: Getter ve setter metotları (veya @property dekoratörü) ile verilerimize kontrollü erişim sağlayarak kapsüllemeyi daha da güçlendirebiliriz.

Soyut Sınıflar ve Soyut Metotlar

Soyutlama Nedir?

Soyutlama, karmaşık sistemleri basitleştirme ve önemli detaylara odaklanma prensibidir. Yazılımda, soyutlama ile nesnelerin nasıl çalıştığıyla değil, ne yaptığıyla ilgileniriz. İç detayları saklar, sadece dış arayüzü gösteririz.

Bir araba kullanmak gibi düşünün: Arabayı nasıl çalıştırdığınızı (motorun iç mekanizması, yakıt sistemi vb.) bilmenize gerek yoktur. Sadece direksiyon, gaz, fren gibi arayüzleri kullanarak arabayı kullanabilirsiniz.

Soyut Sınıflar ve Metotlar

  • Soyut Sınıf (Abstract Class): Tamamlanmamış, sadece bir taslak olan sınıflardır. Soyut sınıflar doğrudan örneklenemez (nesne oluşturulamaz). Sadece alt sınıflar tarafından kalıtım yoluyla kullanılabilirler.
  • Soyut Metot (Abstract Method): Soyut sınıf içinde tanımlanan, gövdesi olmayan metotlardır. Soyut metotlar, alt sınıflar tarafından uygulanmak (override edilmek) zorundadır.

Neden Soyut Sınıflar ve Metotlar?

  • Arayüz Tanımlama: Soyut sınıflar, alt sınıfların uyması gereken bir arayüz (interface) tanımlar. Bu, farklı sınıfların belirli davranışları garanti etmesini sağlar.
  • Tasarım ve Planlama: Soyut sınıflar, büyük projelerde sınıf hiyerarşisini ve ilişkilerini planlamak için kullanışlıdır. Ortak özellikleri soyut sınıfta tanımlar, farklılıkları alt sınıflara bırakırız.
  • Kodun Tekrar Kullanılabilirliği ve Bakımı: Soyut sınıflar, ortak kodun tekrar kullanılmasını sağlar ve kodun bakımını kolaylaştırır.

abc Modülü (Abstract Base Classes)

Python’da soyut sınıfları ve metotları tanımlamak için abc (Abstract Base Classes) modülünü kullanırız.

  • abc.ABC: Soyut sınıf oluşturmak için kullanılan temel sınıf. Sınıfımızı ABC’den türeterek soyut sınıf yaparız.
  • @abc.abstractmethod: Metodu soyut yapmak için kullanılan dekoratör. Soyut metotların gövdesi olmaz (pass ifadesi kullanılır).

Soyut Sınıf ve Metot Örneği (Python)

from abc import ABC, abstractmethod

class Sekil(ABC): # Sekil soyut sınıfı
    @abstractmethod
    def alan_hesapla(self): # Soyut metot
        pass

    @abstractmethod
    def cevre_hesapla(self): # Soyut metot
        pass
class Dikdortgen(Sekil): # Dikdortgen somut sınıfı, Sekil'den türedi
    def __init__(self, uzunluk, genislik):
        self.uzunluk = uzunluk
        self.genislik = genislik

    def alan_hesapla(self): # Soyut metodu uygula (override)
        return self.uzunluk * self.genislik

    def cevre_hesapla(self): # Soyut metodu uygula (override)
        return 2 * (self.uzunluk + self.genislik)
class Daire(Sekil): # Daire somut sınıfı, Sekil'den türedi
    def __init__(self, yaricap):
        self.yaricap = yaricap

    def alan_hesapla(self): # Soyut metodu uygula (override)
        return 3.14 * self.yaricap**2

    def cevre_hesapla(self): # Soyut metodu uygula (override)
        return 2 * 3.14 * self.yaricap
# sekil1 = Sekil() # Hata! Soyut sınıf örneklenemez.

dikdortgen1 = Dikdortgen(5, 10)
print(f"Dikdörtgen Alan: {dikdortgen1.alan_hesapla()}")
print(f"Dikdörtgen Çevre: {dikdortgen1.cevre_hesapla()}")

daire1 = Daire(3)
print(f"Daire Alan: {daire1.alan_hesapla()}")
print(f"Daire Çevre: {daire1.cevre_hesapla()}")

# class Ucgen(Sekil): # Ucgen sınıfı Sekil'den türedi ama soyut metotları uygulamadı
#     pass # Hata! Soyut metotlar uygulanmalı.

Özet: Soyut sınıflar ve metotlar ile ortak arayüzler tanımlayabilir, kod tekrarını azaltabilir ve daha iyi bir sınıf hiyerarşisi oluşturabiliriz.

Uygulamalı Örneklerle Kapsülleme ve Soyutlamanın Önemi

Örnek 1: Banka Hesap Yönetim Sistemi

Banka hesap yönetim sistemi düşünelim. Hesap bilgilerini (bakiye, hesap numarası vb.) kapsülleyerek yetkisiz erişimi engelleriz. Hesap işlemleri (para yatırma, çekme) için ise soyut metotlar tanımlayarak farklı hesap türlerinin (vadeli, vadesiz vb.) aynı arayüzü kullanmasını sağlarız.

Kapsülleme Faydası:

  • Bakiye bilgisini private yaparak, doğrudan dışarıdan değiştirilmesini engelleriz. Sadece para_yatir() ve para_cek() gibi kontrollü metotlar ile bakiye güncellenebilir.
  • Hesap numarası gibi hassas bilgileri de kapsülleyerek, yanlışlıkla veya kötü niyetli kullanımların önüne geçeriz.

Soyutlama Faydası:

  • Hesap soyut sınıfı tanımlayarak, tüm hesap türlerinin (VadeliHesap, VadesizHesap) bakiye_goruntule(), para_yatir(), para_cek() gibi temel işlemleri gerçekleştirmesini garanti ederiz.
  • Yeni bir hesap türü eklemek istediğimizde, sadece Hesap soyut sınıfından türetip soyut metotları uygulamamız yeterli olur. Sistem genelinde tutarlılık sağlanır.

Örnek 2: Çeşitli Medya Oynatıcıları

Farklı medya oynatıcıları (video oynatıcı, müzik oynatıcı) düşünelim. Oynatma, durdurma, ses ayarı gibi ortak işlemleri soyutlayarak, farklı oynatıcı türlerinin aynı temel arayüzü kullanmasını sağlarız.

Soyutlama Faydası:

  • MedyaOynatici soyut sınıfı tanımlayarak, tüm oynatıcı türlerinin (VideoOynatici, MuzikOynatici) oynat(), durdur(), ses_ayarla() gibi temel işlemleri gerçekleştirmesini garanti ederiz.
  • Uygulama içinde, hangi oynatıcı türünü kullandığımızdan bağımsız olarak, soyut MedyaOynatici arayüzü üzerinden işlemleri gerçekleştirebiliriz. Kod daha esnek ve modüler olur.

Kapsülleme ve Soyutlamanın Önemi

  • Daha Güvenli Yazılımlar: Kapsülleme ile veri güvenliğini artırır, yetkisiz erişimleri engelleriz.
  • Daha Düzenli ve Anlaşılır Kod: Modülerlik artar, kodun okunabilirliği ve bakımı kolaylaşır.
  • Daha Esnek ve Genişletilebilir Sistemler: Soyutlama ile sistemler daha esnek hale gelir, yeni özellikler eklemek kolaylaşır.
  • Kodun Tekrar Kullanılabilirliği: Soyut sınıflar ve arayüzler sayesinde kod tekrarı azalır.

Sonuç: Kapsülleme ve soyutlama, nesne tabanlı programlamanın temel taşlarıdır. Bu prensipleri doğru kullanarak, daha kaliteli, sürdürülebilir ve güvenilir yazılımlar geliştirebiliriz.

Haftaya Görüşmek Üzere!