Ahmet Balaman LogoAhmet Balaman

Dart'ta Kalıtım (Inheritance) ve Polymorphism - Baba-Çocuk İlişkisi

personAhmet Balaman
calendar_today
DartOOPInheritancePolymorphismFlutter

Kalıtım ve Polymorphism ile Boğuşmam

Bugün Dart'ta nesne yönelimli programlamanın (OOP) kalbine dokundum: Kalıtım (Inheritance) ve Polymorphism. Başta biraz karmaşık gelse de örnekler üzerinde çalışınca gayet mantıklı gelmeye başladı.

Canlı Demo: Inheritance ve Polymorphism

Kalıtım ve polymorphism kavramlarını interaktif olarak deneyin:

Kalıtım (Inheritance) Nedir?

Kalıtımı en basit haliyle şöyle açıklayabilirim: Baba-çocuk ilişkisi. Babanın özellikleri çocuğa geçer. Dart'ta buna extends diyoruz.

SuperClass ve SubClass

  • SuperClass: Üst sınıf, baba sınıf
  • SubClass: Alt sınıf, çocuk sınıf

Çocuk (SubClass), babasından (SuperClass) özellikler alır ama babası çocuktan alamaz. Tek yönlü bir ilişki yani.

İlk Kalıtım Örneğim: Ev Ailesi

class Ev {
  late int penceresayisi;
  
  Ev(this.penceresayisi);
}

class Saray extends Ev {
  int? kulesayisi;
  
  Saray(this.kulesayisi, int penceresayisi) : super(penceresayisi);
}

class Villa extends Ev {
  bool? garajvarmi;
  
  Villa(this.garajvarmi, int penceresayisi) : super(penceresayisi);
}

void main() {
  var ev1 = Ev(4);
  print("Normal ev: ${ev1.penceresayisi} pencere");
  
  var saray1 = Saray(3, 50);
  print("Saray: ${saray1.kulesayisi} kule, ${saray1.penceresayisi} pencere");
  
  var villa1 = Villa(true, 12);
  print("Villa: Garaj var mı? ${villa1.garajvarmi}, ${villa1.penceresayisi} pencere");
}

Burada şunu fark ettim: Saray ve Villa sınıfları Ev sınıfından türüyor. Her ikisinde de penceresayisi var çünkü bunu babadan (Ev) alıyorlar. Ama her birinin kendine özgü özellikleri var (kule sayısı, garaj).

Super Anahtar Kelimesi

super() babanın constructor'ını çağırıyor. Yani "baba, senin özelliklerin için gerekli parametreleri buraya verdim" diyoruz.

@override - Çocuk Babasının Metodunu Değiştirebilir

Override konusu çok ilginç. Çocuk, babasından gelen bir metodu beğenmeyebilir ve kendi versiyonunu yazabilir.

Hayvan Sesleri Örneği

Bu örneği yaparken çok eğlendim:

class Hayvan {
  void sescikar() {
    print("Ses yok");
  }
}

class Memeli extends Hayvan {}

class Kedi extends Memeli {
  @override
  void sescikar() {
    print("Miyav");
  }
}

class Kopek extends Memeli {
  @override
  void sescikar() {
    print("Hav hav");
  }
}

class Tilki extends Memeli {
  // Tilkinin sesi yok, babasından alacak
}

void main() {
  var kedi1 = Kedi();
  var kopek1 = Kopek();
  var tilki1 = Tilki();
  var hayvan1 = Hayvan();
  
  kedi1.sescikar();    // Miyav
  kopek1.sescikar();   // Hav hav
  tilki1.sescikar();   // Ses yok (babadan aldı)
  hayvan1.sescikar();  // Ses yok
}

Override'ın Mantığı

Kedi ve Köpek kendi seslerini tanımladılar (override ettiler). Ama Tilki için özel bir ses tanımlamadık, o yüzden üst sınıfın metodunu kullandı.

Dart şöyle çalışıyor:

  1. Önce kendi sınıfına bakıyor, metod var mı?
  2. Yoksa bir üstteki sınıfa bakıyor
  3. Orada da yoksa daha üsttekine bakıyor
  4. En üstte bile yoksa hata veriyor

Polymorphism - Kafam Biraz Karıştı

Polymorphism konusunu tam anlamadığımı söylemeliyim. Ama şu kadarını kavradım: Bir babanın tipinde, çocuğun davranışını gösterebiliyoruz.

Hayvan h = Kedi();
h.sescikar(); // "Miyav" yazar

Burada h değişkeni Hayvan tipinde ama aslında bir Kedi nesnesi. Bu da çağırdığımızda Kedi'nin sesini çıkarıyor.

Neden Kullanıyoruz?

Araştırdığım kadarıyla şu durumlarda işe yarıyor:

  • Liste yapılarında farklı alt sınıfları aynı listede tutmak
  • Fonksiyonlara parametre gönderirken esneklik sağlamak
void hayvanSesiCikar(Hayvan hayvan) {
  hayvan.sescikar();
}

void main() {
  hayvanSesiCikar(Kedi());    // Miyav
  hayvanSesiCikar(Kopek());   // Hav hav
  hayvanSesiCikar(Tilki());   // Ses yok
}

Fonksiyon Hayvan tipinde parametre alıyor ama ona hem Kedi, hem Köpek, hem Tilki gönderebiliyoruz. İşte bu polymorphism.

Tip Kontrolü ve Casting

Bir nesnenin hangi tipte olduğunu kontrol etmek için is kullanıyoruz:

var saray1 = Saray(3, 50);

if (saray1 is Saray) {
  print("Bu bir saray");
}

if (saray1 is Ev) {
  print("Bu aynı zamanda bir ev"); // Bu da çalışır!
}

Upcasting ve Downcasting

Bu isimleri ilk duyduğumda "ne alakası var" dedim ama mantığını anladım:

Upcasting: Çocuğu babaya çevirmek (otomatik olur)

Ev ev = saray1; // Saray'ı Ev tipine çevirdik

Downcasting: Babayı çocuğa çevirmek (zorla yapılır)

Ev ev1 = Ev(10);
Villa yeniVilla = ev1 as Villa; // Zorla Villa'ya çevirdik

Uyarı: Downcasting risklidir! Eğer ev1 gerçekten bir Villa değilse hata alırsınız.

Bugün Öğrendiklerim

  • Kalıtım ile kod tekrarını önlüyoruz
  • @override ile babadan gelen metodları değiştirebiliyoruz
  • Polymorphism esneklik sağlıyor (tam anlayamadım ama)
  • is ile tip kontrolü yapabiliyoruz
  • Upcasting ve Downcasting ile tipler arası geçiş yapıyoruz

Polymorphism konusunu daha iyi anlamak için daha fazla pratik yapmam gerekiyor. Yarın Interface'ler ve Abstract class'lara bakacağım.

Flutter'da Nerede Kullanıyoruz?

Flutter'da widget'lar da aslında kalıtım kullanıyor:

class MyWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container();
  }
}

StatelessWidget bizim SuperClass'ımız, MyWidget ise SubClass.


Soru ve önerileriniz için:

Bir sonraki yazıda görüşmek üzere!