Bölüm 12 / 15
Kalıtım
base/derived, base çağrısı, sealed, kompozisyon karşılaştırması.
8 dakikalık okuma
İkinci ilke: bir sınıfın, başka bir sınıfın veri ve
davranışını devralıp üzerine kendi özelliklerini eklemesi. Bu derste sözdizimini
(: base), constructor zincirini, protected'ın gerçek
anlamını ve üst tip referansının kapısını açtığı dünyayı kuracaksın. Ders, kalıtımın
reklamıyla bitmiyor — bedelini ve çoğu durumda daha doğru olan alternatifi
(kompozisyon) da öğreneceksin; kıdemliyi acemiden ayıran tam olarak bu dengedir.
01Ne öğreneceksin
- Kalıtım sözdizimi, is-a ilişkisi ve C#'ın tek kalıtım kuralı
base: constructor zinciri ve çalışma sırası;protectederişim- Üst tip referansı:
Kisi k = new Ogrenci()— polimorfizmin kapısı objectherkesin atası;sealed; kalıtım-kompozisyon seçimi
02Sorun: yüzde sekseni aynı iki sınıf
Kurs sistemine eğitmenler eklenecek. Eğitmenin de adı, e-postası, kayıt tarihi var —
Ogrenci'yle birebir aynı; farkı yalnızca branş ve ücret alanları. Elindeki araçlarla
iki yol var, ikisi de kötü: alanları Ogrenci'den Egitmen'e kopyalamak (DRY
ihlali — e-posta doğrulama kuralı değişince iki yerde düzelteceksin, birini
unutacaksın) ya da her şeyi tek "Kisi" sınıfına yığıp Tip alanıyla
ayırmak (her metot if (tip == "ogrenci") dallarıyla dolar). Ortaklığın
tek yerde tanımlanıp farklılıkların üzerine eklenebildiği bir mekanizma gerek.
Kalıtım tam olarak budur.
03Sözdizimi: iki nokta ve is-a
class Kisi // temel sınıf (base class)
{
public string Ad { get; }
public string Eposta { get; set; }
public Kisi(string ad)
{
Ad = ad;
}
public void KendiniTanit()
{
Console.WriteLine($"Ben {Ad}");
}
}
class Ogrenci : Kisi // türetilmiş sınıf (derived class)
{
public int Not { get; private set; }
public Ogrenci(string ad, int not) : base(ad) // önce Kisi kurulur
{
Not = not;
}
}
var aysel = new Ogrenci("Aysel", 85);
aysel.KendiniTanit(); // Ben Aysel — metot Kisi'den DEVRALINDI
Console.WriteLine(aysel.Not); // 85 — kendi eklediği üye
class Ogrenci : Kisi satırı "Ogrenci bir Kisi'dir" der — buna
is-a ilişkisi denir ve kalıtımın tek meşru gerekçesidir. Ogrenci, Kisi'nin
public ve protected tüm üyelerini devralır (private üyeler devralınır ama erişilemez —
kapsülleme kalıtımda da geçerli), üzerine kendi üyelerini ekler. Terimleri eşle:
temel/üst sınıf (base), türetilmiş/alt sınıf (derived).
İki dil kuralı: C#'ta bir sınıf yalnızca bir sınıftan türeyebilir (tek
kalıtım — çoklu kalıtımın elmas problemine karşı dil kararı; birden çok "yetenek"
ihtiyacı 14. derste interface'lerle çözülecek). Ve kalıtım zinciri derinleşebilir:
YuksekLisansOgrencisi : Ogrenci : Kisi — ama derin hiyerarşi bir tasarım
uyarısıdır, dersin sonunda geleceğiz.
04base: kurucu zinciri ve sıra
Türetilmiş sınıf kurulurken önce temel sınıfın constructor'ı çalışır —
Ogrenci var olmadan önce ortada geçerli bir Kisi olmalıdır. : base(ad)
yazımı, Kisi'nin hangi kurucusunun hangi argümanlarla çağrılacağını söyler. Temel
sınıfın parametresiz kurucusu yoksa bu zorunludur; yazmayı unutursan derleyici
"Kisi'nin 1 argüman alan kurucusuna karşılık yok" benzeri bir hatayla durdurur.
Sırayı kanıtla:
class Kisi
{
public Kisi() { Console.WriteLine("1: Kisi kuruldu"); }
}
class Ogrenci : Kisi
{
public Ogrenci() { Console.WriteLine("2: Ogrenci kuruldu"); }
}
new Ogrenci(); // çıktı: önce 1, sonra 2 — her zaman taban önce
"Nesne yaratılırken kurucular hangi sırayla çalışır?" yazılı sınavların sevilen
sorusudur: en üst atadan aşağı doğru. base. öneki metot içinden de
kullanılabilir — türetilmiş sınıf, devraldığı bir üyenin üstüne yazarken orijinaline
base.KendiniTanit() gibi erişir; bunun asıl sahnesi bir sonraki dersin
virtual/override mekanizmasıdır.
05protected: aileye açık, sokağa kapalı
Geçen dersin tablosundaki protected şimdi anlam kazanıyor: üyeyi
dışarıya kapatır ama türetilmiş sınıflara açar:
class Kisi
{
protected DateTime kayitTarihi = DateTime.Now; // dışarıya kapalı, çocuklara açık
}
class Ogrenci : Kisi
{
public int KayitYili()
{
return kayitTarihi.Year; // erişebilir: Ogrenci aileden
}
}
var o = new Ogrenci();
// o.kayitTarihi → DERLEME HATASI: dışarısı aileden değil
Seçim ilkesi geçen dersle aynı — en dar erişim: önce private düşün; türetilmiş sınıfların gerçekten ihtiyacı varsa protected'a genişlet. "Belki alt sınıf ister" diye protected yapılan her üye, temel sınıfın iç yapısını tüm türevlere sözleşme olarak bağlar — değiştirmesi public kadar zorlaşır.
06Üst tip referansı: kapıyı aralamak
is-a ilişkisinin tip sistemine yansıması şudur: Ogrenci olan her şey Kisi'dir, dolayısıyla Kisi beklenen her yere Ogrenci verilebilir:
Kisi k = new Ogrenci("Aysel", 85); // geçerli: Ogrenci bir Kisi'dir
k.KendiniTanit(); // Kisi'nin üyeleri kullanılabilir
// k.Not → DERLEME HATASI: değişkenin TİPİ Kisi;
// derleyici Not'un varlığını garanti edemez
var herkes = new List<Kisi> // asıl güç: karışık liste
{
new Ogrenci("Aysel", 85),
new Egitmen("Deniz", "Yazılım")
};
foreach (var kisi in herkes)
{
kisi.KendiniTanit(); // ortak yüzey üzerinden tek tip kod
}
İnce ama temel ayrım: değişkenin tipi Kisi, gösterdiği nesne
Ogrenci'dir. Derleyici sana yalnızca tipin garanti ettiği üyeleri kullandırır; nesnenin
fazlası (Not) oradadır ama bu referanstan görünmez. "Peki k.KendiniTanit()
çağrısı Ogrenci kendi tanıtımını yazmak isterse ne olacak?" — işte o soru bir sonraki
dersin tamamıdır: polimorfizm. Bu bölümdeki List<Kisi> kalıbını
sindir; virtual/override onun üzerine oturacak.
07object: herkesin atası; sealed: soyun sonu
C#'ta ata belirtmeyen her sınıf, örtük olarak object'ten türer —
dolayısıyla her şey bir object'tir: int de, string de, senin Ogrenci'n de.
Herkesin ToString(), Equals(), GetType()
üyelerine sahip olmasının sebebi budur; Console.WriteLine(nesne)
çalışabiliyor çünkü her nesneye "kendini yaz" diye sorabiliyor (varsayılan cevabın
neden tip adı olduğu ve nasıl düzeltileceği — ToString override — gelecek ders).
Madalyonun öbür yüzü sealed: bir sınıfı kalıtıma kapatır —
sealed class OdemeServisi'nden türemeye çalışmak derleme hatasıdır.
Ne zaman? Sınıf kalıtım düşünülerek tasarlanmadıysa. Kalıtıma açık sınıf bir
sözleşmedir: hangi üyeler override edilebilir, iç durum türevlerden nasıl korunur —
bunlar düşünülmemişse açık bırakmak gelecekteki türevlere kırık zemin bırakmaktır.
.NET'in kendisi de string'i sealed yapmıştır. Pratik ilke: kalıtım
için tasarlamadıysan mühürle ya da en azından türetmeyi teşvik etme.
08Bedel ve alternatif: kompozisyon
Kalıtım güçlüdür ve tam bu yüzden en çok kötüye kullanılan OOP aracıdır. Bedeli: en sıkı bağdır — türev, temelin iç işleyişine kaynaşır; temeldeki her değişiklik tüm türevlere dalga dalga yayılır. Derin hiyerarşiler (4-5 seviye) "bu davranış nereden geliyor?" sorusunu arkeolojiye çevirir.
Turnusol testi is-a cümlesidir — ve dürüst uygulanır: "Araba bir Motor'dur" yanlış; araba motora sahiptir (has-a). Sahiplik ilişkisi kalıtımla değil kompozisyonla kurulur — sınıf, diğerini üye olarak içerir:
// YANLIŞ: kod paylaşmak için kalıtım
class Rapor : List<string> { } // Rapor bir List midir? Hayır — listeye sahiptir.
// Üstelik Clear() gibi 30 üye yüzeyine sızdı.
// DOĞRU: kompozisyon — içer, yalnızca gerekeni sun
class Rapor
{
private readonly List<string> satirlar = new(); // geçen dersin kalıbı
public void SatirEkle(string satir) => satirlar.Add(satir);
public int SatirSayisi => satirlar.Count;
}
Sektörün özdeyişi "composition over inheritance"tır (kompozisyonu kalıtıma tercih et): kalıtım is-a ilişkisi ve ortak davranışın gerçekten tek gövdede toplanması gerektiğinde; kod paylaşımı için ise kompozisyon. Kisi→Ogrenci meşru kalıtımdır; Rapor→List kod paylaşımı hilesidir. Bu ayrımı Hat 04'te LSP dersi (tas-solid-lsp) kurallaştıracak; sezgisini bugün kur.
09Mini alıştırma — Kisi hiyerarşisi
Yaklaşık 20 dakika · dotnet new console -o kalitim · gelecek ders bu kodla devam edecek
Kisisınıfını yaz:Ad(get-only), protectedkayitTarihi(constructor'daDateTime.Now),void KendiniTanit(). Constructor'lara birerConsole.WriteLine("Kisi kuruldu")tarzı iz koy.Ogrenci : Kisi(Not — private set, 0-100 guard'lı) veEgitmen : Kisi(Brans) sınıflarını türet; ikisinde de: base(ad)zincirini kur ve kuruculara iz koy. Bir Ogrenci yarat, izlerin sırasını gözle: önce Kisi, sonra Ogrenci.List<Kisi>'ye iki öğrenci bir eğitmen koy; foreach ile herkeseKendiniTanit()çağır. Sonra döngü içindekisi.Not'a erişmeyi dene — derleme hatasını oku ve neden makul olduğunu (tip garantisi) bir cümleyle açıkla.: base(ad)çağrısını silip derleme hatasını gör; geri koy.Egitmen'isealedyap, ondan sınıf türetmeyi dene, hatayı gör, türetme denemesini sil.- Kompozisyon karşılaştırması:
KursProgrami : List<string>yazıp nesnesindeClear()'ın kullanılabilir olduğunu IntelliSense'te gör; sonra aynı sınıfı kompozisyonla (private liste + DersEkle/DersSayisi) yeniden yaz. Hangi sürümün yüzeyi kontrol ettiğini not et. Projeyi silme — gelecek ders virtual/override bu dosyaların üzerine eklenecek.
10Kendini sına
Nesne yaratılırken constructor'lar hangi sırayla çalışır? : base(...) ne işe yarar?
Önce temel sınıfın kurucusu, sonra türetilmişinki — zincir en
üst atadan aşağı iner; türev var olmadan önce geçerli bir taban kurulmalıdır.
: base(...), temel sınıfın hangi kurucusunun hangi argümanlarla
çağrılacağını belirtir; temel sınıfın parametresiz kurucusu yoksa yazılması
zorunludur, yoksa derleme hatası oluşur.
protected ile private arasındaki fark nedir? protected'ın gizli maliyeti nedir?
private üyeye yalnızca sınıfın kendisi erişir; protected üyeye sınıf ve ondan türeyen sınıflar erişir, dışarısı erişemez. Maliyeti: protected üye, tüm türevlerin bağlandığı bir iç sözleşme haline gelir — değiştirmek public üye değiştirmek kadar risklidir. Bu yüzden önce private düşünülür, gerçek ihtiyaçta protected'a genişletilir.
Kisi k = new Ogrenci(...) geçerli midir? k.Not neden derlenmez?
Geçerlidir — Ogrenci bir Kisi'dir (is-a), üst tip referansı
alt tip nesneyi gösterebilir. k.Not derlenmez çünkü derleyici üye
erişimini nesnenin gerçek tipine değil değişkenin tipine göre denetler:
Kisi tipi Not'u garanti etmez. Nesnede Not vardır ama bu referanstan görünmez;
bu ayrım polimorfizmin (sonraki ders) temelidir.
Kalıtım ile kompozisyon arasında nasıl seçim yaparsın? Rapor : List<string> neden kötü tasarımdır?
Test is-a cümlesidir: "X bir Y'dir" dürüstçe doğruysa ve ortak davranış tek gövdede toplanacaksa kalıtım; ilişki "sahiptir" ise kompozisyon — sınıf diğerini private üye olarak içerir. Rapor bir List değildir, satır listesine sahiptir; kalıtım burada kod paylaşımı hilesidir ve List'in tüm yüzeyini (Clear dahil) Rapor'a sızdırarak kapsüllemeyi de bozar. Özdeyiş: composition over inheritance.
11Sınav köşesi
Yazılıda nasıl sorulur
Dört klasik: (1) Kurucu sırası — "türetilmiş sınıf nesnesi yaratılınca hangisi önce çalışır?" (temel sınıfın kurucusu). (2) Erişim — "temel sınıfın private üyesine türetilmiş sınıftan erişilebilir mi?" (hayır; protected erişilir). (3) Dil kuralı — "C# çoklu kalıtımı destekler mi?" (sınıflar için hayır, tek kalıtım; çoklu ihtiyaç interface ile). (4) sealed — "kalıtıma kapatılan sınıf hangi anahtar kelimeyle işaretlenir?". Üst tip referanslı kod parçasında hangi satırın derlenmediğini bulmak da sorulur — tip/nesne ayrımını bilen çözer.
Sözlüde nasıl anlatılır — 2 dakikalık taslak
"Kalıtımı anlatır mısınız?" sorusunda parlamanın yolu dengedir: (1) Tanım — ortak veri ve davranışın temel sınıfta toplanması, türevlerin üzerine eklemesi; is-a ilişkisi şart. (2) Mekanik — base ile kurucu zinciri, tabanın önce kurulduğu; protected'ın aileye açık erişim olduğu. (3) Üst tip referansının karışık listeleri tek kodla işletmeyi açtığını söyle — "polimorfizmin zemini" cümlesi konuyu bütünlüklü bildiğini gösterir. (4) Mutlaka bedeliyle kapat: "Kalıtım en sıkı bağdır; is-a yoksa kompozisyonu tercih ederim, kalıtım için tasarlamadığım sınıfı sealed yaparım." Bu son cümleyi mülakatta çok az aday kurar.
