Skip to content

Bölüm 11 / 15

Kapsülleme

public/private/protected/internal; neden gizleriz.

8 dakikalık okuma

Dört ilkenin ilki. Geçen ders property ve private set ile pratiğine başladın; bu ders mekanizmayı tamamlıyor: dört erişim belirleyici, "neden gizleriz" sorusunun üç mühendislik cevabı ve çoğu kıdemlinin bile düştüğü koleksiyon sızıntısı tuzağı. Kapsülleme, "sınıfın kuralını sınıfın kendisi korur" fikrinin teknik altyapısıdır — İlan B'nin OOP maddesinin ve her C# mülakatının çekirdeği.

01Ne öğreneceksin

  • Invariant kavramı: sınıfın her an doğru kalması gereken kuralları
  • Dört erişim belirleyici — public, private, protected, internal — ve varsayılanlar
  • Gizlemenin üç gerekçesi: kural koruma, iç yapıyı değiştirme serbestisi, küçük yüzey
  • Koleksiyon sızıntısı tuzağı ve IReadOnlyList çözümü

02Invariant: sınıfın koruduğu söz

Geçen dersin BankaHesabi'sini hatırla: bakiye negatif olamaz, para çekme bakiyeyi aşamaz. Bu tür kurallara — nesne ne yaşarsa yaşasın her an doğru kalması gereken şartlara — invariant denir. Örnekler: öğrencinin notu 0-100 arasındadır; siparişin toplam tutarı satırlarının toplamına eşittir; stok adedi negatif olamaz.

Kapsüllemenin özü tek cümledir: invariant'ı ancak, veriye giden her yol senin kontrolündeyse koruyabilirsin. Alan public olduğu sürece hesap.bakiye = -5000; yazan bir satırı hiçbir şey durduramaz — kural, onu bilen herkesin iyi niyetine emanettir ve büyüyen kod tabanında iyi niyet ölçek almaz. Çözüm, veriyi kapatıp erişimi kurallı kapılardan (property'ler, metotlar) geçirmektir. Geçen ders bunu sezgiyle yaptın; şimdi araç setinin tamamını öğreniyorsun.

03Dört erişim belirleyici

BelirleyiciKim erişebilir?Tipik kullanım
publicHerkesSınıfın dışarıya sunduğu yüzey: property'ler, metotlar
privateYalnızca sınıfın kendisiAlanlar, iç yardımcı metotlar — varsayılan tercih
protectedSınıfın kendisi + ondan türeyen sınıflarKalıtımla anlam kazanır — bir sonraki ders
internalAynı proje (assembly) içindeki her kodProjeye özel, dışarıya kapalı tipler

İki varsayılanı ezbere bil — yazılı sınav klasiğidir: belirleyici yazılmazsa sınıf üyeleri private, sınıfın kendisi internal olur. Yani C# güvenli tarafta başlar: açmadıkça kapalıdır. internal'ın anlamını bugün kabaca bil (proje = derlenmiş çıktı birimi, assembly); birden çok projeli çözümlerde (Hat 05, net-proje-yapisi) gerçek değerine kavuşur. protected internal ve private protected kombinasyonları da vardır; günlük kodda nadirdir, isimlerini tanıman yeterli.

Seçim ilkesi mekaniktir: her üyeye verebileceğin en dar erişimi ver. "Belki lazım olur" diye public yapılan her üye, yarın kaldıramayacağın bir söz olur — birileri ona bağlanır.

04Neden gizleriz: üç gerekçe

1 — Kural tek yerde korunur. Not'un 0-100 doğrulaması set'teyse geçersiz değer hiçbir yoldan giremez; kontrol, veriyi kullanan 40 yerde değil veriyi tutan tek yerde yaşar. 9. dersin "kural kim korur?" sorusunun mühendislik cevabı budur: derleyici + sınıf tasarımı korur, insan hafızası değil.

2 — İç yapı değişebilir, dışarısı bozulmaz. Sınıfın public yüzeyi bir sözleşmedir; private kısmı istediğin gibi değiştirebilirsin. Örnek: bugün OrtalamaNot property'si her çağrıda hesap yapıyor olsun; yarın performans için sonucu bir private alanda saklamaya karar verdin. Dışarıdaki hiçbir satır değişmez — çünkü dışarısı yalnızca property'yi biliyor, arkasındaki mekanizmayı değil. Public field'da bu serbesti yoktur: iç yapın, sözleşmenin kendisi haline gelmiştir.

3 — Küçük yüzey, küçük bakım yükü. Bir sınıfın public üyeleri, onu kullanan herkesin bağlandığı noktalardır; her public üye, sonsuza dek desteklenecek bir vaattir. Yüzey küçüldükçe sınıfı anlamak, test etmek ve değiştirmek kolaylaşır. Kod incelemesinde "bu üye neden public?" sorusunun her zaman bir cevabı olmalıdır; "öylesine" cevabı private'a çevirme talimatıdır.

05Araç seti: private set, get-only, readonly, init

Kapsülleme spektrum işidir — "tam açık" ile "tam kapalı" arasında doğru noktayı seçersin. C#'ın kademeleri:

class Ogrenci
{
    public string Ad { get; }                  // get-only: yalnızca constructor'da atanır
    public int Not { get; private set; }       // dışarıya okunur, yazma sınıf içinde
    public string SinifKodu { get; set; }      // tam açık: kuralsız veri için
    public string Eposta { get; init; }        // init: yalnızca new anında atanabilir
    private readonly List<int> gecmisNotlar = new();   // readonly: referansı değişemez

    public Ogrenci(string ad, int baslangicNotu)
    {
        Ad = ad;
        NotGuncelle(baslangicNotu);
    }

    public void NotGuncelle(int yeniNot)
    {
        if (yeniNot < 0 || yeniNot > 100) { return; }   // invariant kapıda
        gecmisNotlar.Add(Not);
        Not = yeniNot;                          // private set'e sınıf içinden erişim serbest
    }
}

Kademeleri oku: get; (get-only) doğumdan sonra hiç değişmeyecek kimlik verisi içindir. private set "dışarısı okusun ama değişiklik yalnızca benim metotlarımdan geçsin" der — iş kuralı taşıyan verilerin standardıdır. init, object initializer ile new anında atamaya izin verip sonrasını kilitler (modern C#; ayrıntısı record'larla birlikte Hat 03'te). readonly field'da ise incelik vardır: referansı kilitler, içeriği değil — gecmisNotlar'a yeni liste atanamaz ama Add çağrılabilir. Bu ayrım bizi dersin en önemli tuzağına getiriyor.

06Tuzak: koleksiyon sızıntısı

Şu sınıf, kapsüllemeyi yaptığını sanan kodun klasiğidir:

class Kurs
{
    public List<Ogrenci> Ogrenciler { get; } = new();   // get-only, güvenli... değil mi?

    public void KayitEkle(Ogrenci o)
    {
        if (Ogrenciler.Count >= 30) { return; }         // kural: kontenjan 30
        Ogrenciler.Add(o);
    }
}

var kurs = new Kurs();
kurs.Ogrenciler.Add(new Ogrenci("Kaçak", 0));   // kural ATLANDI — get-only'ye rağmen!
kurs.Ogrenciler.Clear();                         // hatta tüm kayıtlar silinebilir

Neden? 6. dersin kuralı: get-only olan şey referanstır — dışarısı listeyi değiştiremiyor değil, değişkeni değiştiremiyor. Listenin kendisine ulaşan herkes içeriğiyle oynayabilir; KayitEkle'deki kontenjan kuralı süs olmuştur. Kapının kilitli, pencerenin ardına kadar açık olduğu ev budur.

Çözüm: içeriye gerçek listeyi, dışarıya salt okunur görünümünü ver:

class Kurs
{
    private readonly List<Ogrenci> ogrenciler = new();
    public IReadOnlyList<Ogrenci> Ogrenciler => ogrenciler;   // salt okunur pencere

    public void KayitEkle(Ogrenci o)
    {
        if (ogrenciler.Count >= 30) { return; }
        ogrenciler.Add(o);
    }
}

// kurs.Ogrenciler[0]        → okunur, foreach çalışır
// kurs.Ogrenciler.Add(...)  → DERLEME HATASI: IReadOnlyList'te Add yok

IReadOnlyList<T>, listenin okuma üyelerini (indeks, Count, foreach) sunan ama yazma üyelerini hiç içermeyen bir arayüzdür — arayüz kavramının kendisi 14. derste; bugün kullanım yeter. Kural olarak yaz: bir sınıf, iç koleksiyonunu asla olduğu gibi dışarı vermez. Kod incelemelerinde public List<T> görmek, bu dersi bilenler için düzeltme işaretidir.

07Yüzeyi küçük tutmak: private metotlar

Kapsülleme yalnızca veri işi değildir. Bir metot yalnızca sınıfın iç işleyişine hizmet ediyorsa — bir hesabın ara adımı, bir doğrulamanın parçası — private olmalıdır:

class MaasHesaplayici
{
    public decimal NetMaas(decimal brut)               // dışarıya TEK kapı
    {
        return brut - VergiKes(brut) - SgkKes(brut);
    }

    private decimal VergiKes(decimal brut) => brut * 0.15m;   // iç adımlar gizli
    private decimal SgkKes(decimal brut) => brut * 0.14m;
}

Dışarısı yalnızca NetMaas'ı bilir. Yarın vergi hesabı üç metoda bölünse, oranlar tablodan okunsa, dışarıdaki hiçbir kod etkilenmez. 8. dersin "tek iş" ilkesiyle birleşince şablon şudur: public metotlar sınıfın ne yaptığını, private metotlar nasıl yaptığını anlatır. Okunabilirlik de kazanır: sınıfı anlamak isteyen, önce yalnızca public üyeleri okur.

08Mini alıştırma — Stok sınıfı

Yaklaşık 20 dakika · dotnet new console -o kapsulleme

  1. Urun sınıfı: Ad (get-only), Adet (get + private set; negatif olamaz — invariant). Metotlar: void Giris(int adet) (0 ve altını guard'la reddet), bool Cikis(int adet) (geçersizse ya da stok yetmezse false).
  2. Depo sınıfı: private readonly List<Urun> urunler + dışarıya IReadOnlyList<Urun>; UrunEkle (aynı adda ürün varsa ekleme — foreach ile kontrol et), Urun Bul(string ad).
  3. Saldırı testi yaz: dışarıdan depo.Urunler.Add(...), depo.Urunler.Clear() ve urun.Adet = -5 dene — üçünün de derleme hatası verdiğini gör. Hata listesini oku: derleyici artık senin kural bekçin.
  4. Sızıntı deneyi: Depo'daki property'yi bilerek public List<Urun>'e çevir, saldırı satırının derlendiğini ve kuralın delindiğini gör; sonra IReadOnlyList'e geri dön. Farkı bir cümleyle not et.
  5. Kapanış: Depo'ya public int ToplamAdet özelliğini ekle (tüm ürünlerin adet toplamı — ok gövdesiyle) ve iç listeye hiç dokunmadan dışarıya özet bilgi vermenin kapsüllemenin ta kendisi olduğunu gör.

09Kendini sına

Dört erişim belirleyiciyi ve erişim yazılmadığında geçerli olan varsayılanları say.

public: herkes. private: yalnızca sınıfın kendisi. protected: sınıf + ondan türeyenler. internal: aynı proje (assembly) içindeki kod. Varsayılanlar: sınıf üyeleri private, tipin kendisi internal — C# açılmadıkça kapalıdır. İlke: her üyeye yetebilecek en dar erişim verilir.

Invariant nedir? Kapsüllemeyle ilişkisini bir örnekle anlat.

Invariant, nesnenin ömrü boyunca her an doğru kalması gereken kuraldır: bakiye negatif olamaz, not 0-100 arasındadır. Kapsülleme bu kuralın korunma mekanizmasıdır: veri private tutulur, değişiklik yalnızca kuralı denetleyen kapılardan (private set'li property, metot) geçer. Veri public olsaydı kural, onu bilen herkesin hafızasına emanet olurdu — tek unutma, geçersiz durum demektir.

Get-only bir List property'si neden kapsülleme sağlamaz? Doğrusu nedir?

Get-only yalnızca referansın değiştirilmesini engeller; listeye ulaşan herkes Add/Clear ile içeriği değiştirebilir — sınıfın ekleme metodundaki kurallar atlanır. Doğrusu: gerçek liste private readonly tutulur, dışarıya IReadOnlyList<T> olarak sunulur — okuma ve foreach çalışır, yazma üyeleri tip düzeyinde yoktur, ihlal derleme hatasıdır.

"İç yapı değişebilir, dışarısı bozulmaz" gerekçesini bir örnekle açıkla.

Sınıfın public yüzeyi sözleşmedir, private kısmı uygulama ayrıntısıdır. Örnek: her çağrıda hesaplanan bir property, yarın sonucu private alanda saklayacak şekilde değiştirilebilir — dışarıdaki kod property'yi kullanmaya aynen devam eder, tek satır değişmez. Field public olsaydı iç temsil sözleşmenin parçası olurdu ve her değişiklik onu kullanan tüm kodu kırardı.

10Sınav köşesi

Yazılıda nasıl sorulur

İki kalıp hâkim: (1) Erişim tablosu — "private üyeye nereden erişilebilir?", "Erişim belirleyicisi yazılmayan sınıf üyesi ne olur?" (private), "Aynı assembly içinden erişim sağlayan belirleyici?" (internal). (2) Kavram — "Verinin ve onu işleyen metotların bir arada tutulup dış erişimin kısıtlanmasına ne denir?" (kapsülleme). Kod verilen sorularda private üyeye sınıf dışından erişen satırın derleme hatası olduğunu görmen istenir — hatanın türünü (derleme, çalışma zamanı değil) doğru söylemek puanın yarısıdır.

Sözlüde nasıl anlatılır — 2 dakikalık taslak

"Kapsülleme nedir, neden önemlidir?" sorusuna tanım + gerekçe + kanıt düzeni: (1) Tanım: veriyi gizleyip erişimi kontrollü kapılardan geçirmek; dört belirleyiciyi say. (2) Gerekçe üçlüsü: invariant tek yerde korunur; iç yapı serbestçe değişir; public yüzey küçüldükçe bakım ucuzlar. (3) Kanıt olarak koleksiyon sızıntısını anlat: "get-only List kapsülleme değildir, IReadOnlyList ile sunarım" — bu örnek, konuyu ezberden değil pratikten bildiğini tek başına gösterir. Kapanış: "Amacım geçersiz durumu yakalamak değil, yazılamaz kılmak."