Skip to content

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ı; protected erişim
  • Üst tip referansı: Kisi k = new Ogrenci() — polimorfizmin kapısı
  • object herkesin 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

  1. Kisi sınıfını yaz: Ad (get-only), protected kayitTarihi (constructor'da DateTime.Now), void KendiniTanit(). Constructor'lara birer Console.WriteLine("Kisi kuruldu") tarzı iz koy.
  2. Ogrenci : Kisi (Not — private set, 0-100 guard'lı) ve Egitmen : 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.
  3. List<Kisi>'ye iki öğrenci bir eğitmen koy; foreach ile herkese KendiniTanit() çağır. Sonra döngü içinde kisi.Not'a erişmeyi dene — derleme hatasını oku ve neden makul olduğunu (tip garantisi) bir cümleyle açıkla.
  4. : base(ad) çağrısını silip derleme hatasını gör; geri koy. Egitmen'i sealed yap, ondan sınıf türetmeyi dene, hatayı gör, türetme denemesini sil.
  5. Kompozisyon karşılaştırması: KursProgrami : List<string> yazıp nesnesinde Clear()'ı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.