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
| Belirleyici | Kim erişebilir? | Tipik kullanım |
|---|---|---|
public | Herkes | Sınıfın dışarıya sunduğu yüzey: property'ler, metotlar |
private | Yalnızca sınıfın kendisi | Alanlar, iç yardımcı metotlar — varsayılan tercih |
protected | Sınıfın kendisi + ondan türeyen sınıflar | Kalıtımla anlam kazanır — bir sonraki ders |
internal | Aynı proje (assembly) içindeki her kod | Projeye ö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
Urunsı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).Deposınıfı:private readonly List<Urun> urunler+ dışarıyaIReadOnlyList<Urun>;UrunEkle(aynı adda ürün varsa ekleme —foreachile kontrol et),Urun Bul(string ad).- Saldırı testi yaz: dışarıdan
depo.Urunler.Add(...),depo.Urunler.Clear()veurun.Adet = -5dene — üçünün de derleme hatası verdiğini gör. Hata listesini oku: derleyici artık senin kural bekçin. - Sızıntı deneyi:
Depo'daki property'yi bilerekpublic List<Urun>'e çevir, saldırı satırının derlendiğini ve kuralın delindiğini gör; sonraIReadOnlyList'e geri dön. Farkı bir cümleyle not et. - Kapanış:
Depo'yapublic 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."
