Sitemap

Dart Dilinde Nesne Tabanlı Programlama(OOP) — Kalıtım (Inheritance) Kavramı |4.Bölüm

7 min readJun 20, 2025

--

Merhaba, değerli geliştirici arkadaşlar Dart Dilinde Nesne Tabanlı Programlama makale serimizin 4. bölümüyle karşınızdayım.
Geçtiğimiz 3 derste sınıfları(class), kurucu/yapıcıları(constructor) ve kapsülleme(encapsulation) kavramlarını öğrendik. Şimdi Kalıtım(inheritance) kavramını öğreneceğiz.

Bu bir seri olduğu için kaçıranlar ya da tekrar etmek isteyenler için ilk 3 derse aşağıdaki bağlantılardan erişebilirsiniz.

  1. DERS OLAN SINIFLAR İÇİN BURAYA TIKLAYIN
  2. DERS OLAN KURUCU/YAPICILAR İÇİN BURAYA TIKLAYIN
  3. DERS OLAN KAPSÜLLEME İÇİN BURAYA TIKLAYIN

Son dersimiz olan kapsülleme dersinde sınıfımızda bulunan bir özelliği gizli yani private olarak tanımlamayı ve görüntüleme ya da güncelleme işlemleri için getter ve setter kavramlarını öğrenmiştik. Bu sayede sınıflarımızı oluştururken çok daha güvenli ve pratik şekilde yapımızı kurabiliyoruz. Fakat iş burada bitmiyor. Gelin daha da derinlere inelim ve Dart dilinin OOP konusunda bizlere sunduğu diğer özellikleride öğrenelim.

Kalıtım (Inheritance)

Hatırlarsanız örneklerimizi sanki bir sosyal medya uygulaması inşa ediyormuşuz gibi düşünerek verdik. Şimdi uygulamamızı biraz daha büyütelim. Sosyal medya platformumuzda farklı kullanıcı tipleri olduğunu hayal edelim:

  1. Normal Kullanıcı (User): Gönderi paylaşabilir, profiline bakabilir.
  2. Premium Kullanıcı (PremiumUser): Normal kullanıcının her yaptığını yapabilir, ek olarak reklam görmez ve özel içeriklere erişebilir.
  3. Admin Kullanıcı (AdminUser): Normal kullanıcının her yaptığını yapabilir, ek olarak başka kullanıcıları silebilir veya engelleyebilir.

Bu kullanıcılar için sınıf oluşturmak istediğimizde muhtemelen aşağıdaki gibi bir yapı ortaya çıkarırdık

class User {
String username;
String email;
void login() { print('$username giriş yaptı.'); }
void post() { print('Gönderi paylaşıldı.'); }
}

class PremiumUser {
// Ortak özellikler... TEKRAR!
String username;
String email;
void login() { print('$username giriş yaptı.'); }
void post() { print('Gönderi paylaşıldı.'); }

// Ek özellikler
void accessPremiumContent() { print('Özel içeriğe erişildi.'); }
}

class AdminUser {
// Ortak özellikler... YİNE TEKRAR!
String username;
String email;
void login() { print('$username giriş yaptı.'); }
void post() { print('Gönderi paylaşıldı.'); }

// Ek özellikler
void deleteUser(User user) { print('${user.username} silindi.'); }
}

Bu koddaki felaketi görüyor musunuz? username, email, login(), post() gibi onlarca ortak özellik ve metodu her sınıf için kopyala-yapıştır yaptık. Bu durum, yazılım geliştirmenin en büyük düşmanlarından biridir: Kod Tekrarı (Code Duplication).

Değerli arkadaşlar kod tekrarı yapmak okunabilirlik ve anlaşılabilirlik noktasında yazdığımız kodları baltalamakta ve kötü bir kullanıma işaret eder. Yazdığımız kodları anlaşılır ve temiz tutmak ileride kodlarımızın bakımı ve güncellenmesi noktasında bizlere çok fayda sağlamakta. Bu yüzden temiz kod yazmaya özen göstermemiz gerekiyor. Bunu bir alışkanlık yapmak gerekiyor!

Peki neden kötü diye soracak olursanız şöyle izah edeyim; Aynı metotları kullanıyoruz ve metotlardan birini güncellemeyi unuttuğumuzu düşünelim. Uygulamada aksamalara ve problemlere neden olur. Bu tür sorunları aşmak için Kalıtım (Inheritance) devreye girer.

Kalıtım, bir sınıfın (çocuk/alt sınıf) başka bir sınıfın (ebeveyn/üst sınıf) tüm public özelliklerini ve metotlarını miras alarak kendi malı gibi kullanabilmesidir.

Bu sayede ortak kodları sadece bir kez, ebeveyn sınıfta yazarız. Çocuk sınıflar bu ortak kodları otomatik olarak miras alır ve biz sadece onlara özgü ek özellikleri veya davranışları yazarız.

Betimlemek gerekirse; Kalıtımı biyolojik miras gibi düşünebilirsiniz.

  • Ebeveyn (Üst Sınıf): Sizin anne ve babanızdır. Onların göz rengi, saç tipi gibi genetik özellikleri vardır.
  • Çocuk (Alt Sınıf): Sizsiniz. Ebeveynlerinizin genetik özelliklerini (miras) alırsınız. Bu yüzden onlarla ortak özellikleriniz vardır. Ama aynı zamanda size özel, onlarda olmayan yetenekleriniz (örneğin, piyano çalabilmek) de olabilir.

Dart’ta Kalıtım Nasıl Kullanılır?

Dart’ta kalıtım yapmak için iki anahtar kelimeyi bilmemiz gerekir:

  1. extends: Bir sınıfın başka bir sınıftan miras alacağını belirtir. (class Cocuk extends Ebeveyn {})
  2. super: Çocuk sınıfın içinden, bir üstündeki ebeveyn sınıfa erişmek için kullanılır. Ebeveynin constructor’ını veya metotlarını çağırmamızı sağlar.

Şimdi gelin kod üzerinde kullanımını görüp daha iyi pekiştirelim;

// EBEVEYN SINIF (Superclass)
// Tüm ortak özellikleri ve metotları burada topluyoruz.
class User {
String username;
String email;

User(this.username, this.email);

void login() {
print('$username giriş yaptı.');
}
}

// ÇOCUK SINIF 1 (Subclass)
// User'ın tüm özelliklerini miras alıyor.
class PremiumUser extends User {
// PremiumUser, User'dan miras aldığı için username ve email özelliklerine sahip.
// Ama bu özelliklerin başlangıç değerlerini ebeveynin constructor'ına göndermeli.
// İşte 'super' burada devreye giriyor!
PremiumUser(String username, String email) : super(username, email);

// Sadece PremiumUser'a özgü ek metot.
void accessPremiumContent() {
print('$username özel içeriğe erişti.');
}
}

// ÇOCUK SINIF 2 (Subclass)
class AdminUser extends User {
AdminUser(String username, String email) : super(username, email);

// Sadece AdminUser'a özgü ek metot.
void deleteUser(User userToDelete) {
print('$username, ${userToDelete.username} adlı kullanıcıyı sildi.');
}
}

void main() {
var normalUser = User('ali_v', 'ali@veli.com');
var premiumUser = PremiumUser('ayse_y', 'ayse@yilmaz.com');
var adminUser = AdminUser('admin_root', 'admin@site.com');

normalUser.login(); // Çıktı: ali_v giriş yaptı.
premiumUser.login(); // Çıktı: ayse_y giriş yaptı. (User'dan miras aldı!)
adminUser.login(); // Çıktı: admin_root giriş yaptı. (User'dan miras aldı!)

// Herkes kendi özel yeteneğini kullanabilir.
premiumUser.accessPremiumContent(); // Çıktı: ayse_y özel içeriğe erişti.
adminUser.deleteUser(normalUser); // Çıktı: admin_root, ali_v adlı kullanıcıyı sildi.

// HATA: normalUser'ın böyle bir yeteneği yok.
// normalUser.accessPremiumContent();
}

Yukarıdaki kod örneğinde ana sınıfımız User oldu ve User sınıfı içinde diğer sınıfların kalıtım yapacağı özellikleri ekledik. Böylece diğer sınıflarda tek tek aynı yapıları yazmamakla birlikte sınıf içinde sadece o sınıfa özgü özellikler, metotlarda ekledik.

Çok güzel kalıtım kullanmayı öğrendik ama bu miras almayı, kalıtım yapmayı mantıkta ölçüp kalıtıma uygun olup olmadığına karar veremez miyiz? Yani neye kalıtım yapıp neye yapamayacağımızı bilebilir miyiz?
Evet, kalıtımı ne zaman ve neden kullanmamız gerektiğini anlamamızı sağlayan bir kavram var; IS…A(Bir…dir/dır)

IS-A” (Bir…dır) İlişkisi

Bu ilişki, kalıtımı ne zaman ve neden kullanmamız gerektiğini anlamamızı sağlayan bir turnusol kağıdı gibidir. Eğer bu ilişkiyi doğru kuramazsak, yazdığımız kod mantıksız, kırılgan ve yönetilmesi zor bir hale gelir.

“IS-A”, basitçe bir alt sınıfın (çocuk sınıf), üst sınıfın (ebeveyn sınıf) daha özelleşmiş bir versiyonu olduğunu ifade eder. Yani, çocuk sınıf, ebeveynin tüm kimliğini taşır ve üzerine yeni özellikler ekler.

Kendinize şu soruyu sorun: “[Çocuk Sınıf] bir [Ebeveyn Sınıf]’dır.”

Bu cümle kulağa mantıklı ve doğru geliyorsa, kalıtım kullanmak için doğru yoldasınız demektir.

Örneklerle Pekiştirelim:

Doğru Kullanımlar (Mantıklı Cümleler):

  • Kopek bir Hayvan’dır. (Köpek, Hayvan’ın tüm özelliklerini taşır ve ek olarak havla() gibi kendine özgü davranışları vardır.)
  • Dikdortgen bir Sekil’dir. (Dikdörtgen, bir Şekil’dir ve ek olarak kenarUzunlugu gibi özellikleri vardır.)
  • Mercedes bir Araba’dır. (Mercedes, bir Araba’dır ve ek olarak yildizLogoGoster() gibi kendine özgü metotları olabilir.)
  • AdminUser bir User’dır. (Admin, bir Kullanıcı’dır ve ek olarak kullaniciSil() yeteneğine sahiptir.)

Yanlış Kullanımlar (Mantıksız Cümleler):

  • Motor bir Araba’dır. -> YANLIŞ! Motor, Arabanın bir parçasıdır ama bir Araba değildir. Bu ilişki “IS-A” değil, “HAS-A” (Sahiptir) ilişkisidir. (Araba bir Motor’a sahiptir.) “HAS-A” ilişkileri için kalıtım değil, “Composition” (Bileşim) kullanılır. Yani Araba sınıfının içinde Motor türünde bir özellik olur: Motor motor;.
  • Kullanici bir Profil’dir. -> YANLIŞ! Kullanıcı bir Profile sahiptir (User HAS-A Profile), ama Kullanıcı’nın kendisi bir Profil değildir.
  • Kus bir UcmaYetenegi’dir. -> YANLIŞ! Kuş, uçma yeteneğine sahiptir (Bird HAS-A FlyingAbility), ama kendisi bir yetenek değildir. Bu tür “yetenek katma” senaryoları için mixin’ler daha uygundur.

Peki bu ilişkiyi kullanmak zorunda mıyız? Değerli arkadaşlar bu ilişki sizlerin doğru kalıtım yapısını kurabilmeniz adına önemlidir. Bu konuları ilk kez öğreniyorsanız özellikle sizlere bu konuda pratiklik katacaktır.

“IS-A” ilişkisini doğru kurmak, programınızın mantıksal bütünlüğünü sağlar. Eğer bir AdminUser’ın bir User olduğunu söylerseniz, programınızın herhangi bir yerinde User beklenen bir yere AdminUser nesnesi gönderebilirsiniz ve kodunuz bozulmaz. Çünkü AdminUser zaten bir User’dır ve User’ın yapabildiği her şeyi (ve daha fazlasını) yapabilir. Bu bizi doğrudan ikinci önemli kavrama götürür: Çok Biçimlilik.

Çok Biçimlilik (Polymorphism) ve Kalıtım

Kalıtımın getirdiği en büyük güzelliklerden biri de Çok Biçimlilik (Polymorphism)’dir. Bu, “çok formlu olma” anlamına gelir.

Polymorphism (Poli-morfizm), Yunanca’da “çok formlu olma” anlamına gelir. Programlamadaki karşılığı ise şudur: Bir nesnenin, farklı durumlarda farklı şekillerde davranabilmesi veya aynı arayüzün farklı tipler için farklı işlevler görmesi.

Kalıtım bağlamında Polymorphism’in en güçlü tezahürü, bir ebeveyn sınıf referansının, kendisinden türemiş herhangi bir çocuk sınıf nesnesini “işaret edebilmesi” ve bu nesnenin kendi davranışını sergilemesidir.

Bu cümleleri okurken ne anlatıyorsun be abla moduna girmiş olabilirsiniz diye daha basite indirgemeye çalışacağım.

Uzaktan kumanda hayal etmenizi istiyorum. Uzaktan kumandanız bir çok elektronik eşyanızı açmaya ve kapatmaya yarıyor olsun. Yani buradaki ana metotlarımız ac() ve kapa() olsun.

Yani ebeveyn sınıfımızdaki metotlarımız ac ve kapa. Fakat ne dedik bu kumanda birden fazla elektronik eşyayı açıp kapatabiliyor o halde çocuk sınıflarda extends edilebilir.

  • Ebeveyn Sınıf: ElektronikCihaz -> void ac() ve void kapat() metotları var.
  • Çocuk Sınıflar: Televizyon extends ElektronikCihaz, MuzikSeti extends ElektronikCihaz.

Şimdi elinizde sihirli bir “Genel Kumanda” (ElektronikCihaz referansı) olduğunu hayal edin. Bu kumanda tek bir “AÇ” düğmesine sahip.

  1. Kumandayı Televizyon’a doğrultup “AÇ” düğmesine basarsanız ne olur? Televizyon açılır (ekranı aydınlanır).
  2. Kumandayı Müzik Seti’ne doğrultup aynı “AÇ” düğmesine basarsanız ne olur? Müzik Seti açılır (hoparlörlerden ses gelmeye başlar).

Düğme aynı (ac() metodu), arayüz aynı (kumanda), ama sonuçlar farklı! Çünkü nesnenin kendisi (Televizyon veya MuzikSeti) “açılma” eyleminin kendi versiyonunu gerçekleştirir. İşte bu Polymorphism’dir.

Kod üzerinden inceleyelim, umarım daha anlaşılır kılacaktır meseleyi

// Ebeveyn Sınıf
abstract class Hayvan {
String ad;
Hayvan(this.ad);

// Her hayvan bir ses çıkarır, ama nasıl? Bilmiyoruz.
// Bu yüzden bu metodu "override" edilmek (ezilmek) üzere tanımlıyoruz.
void sesCikar() {
print('Belirsiz bir hayvan sesi...');
}
}

// Çocuk Sınıflar
class Kopek extends Hayvan {
Kopek(String ad) : super(ad);

// Ebeveynden gelen metodu eziyoruz (Override).
@override
void sesCikar() {
print('$ad havladı: Hav hav!');
}
}

class Kedi extends Hayvan {
Kedi(String ad) : super(ad);

// Kedi, "sesCikar" eylemini kendi yöntemleriyle yapıyor.
@override
void sesCikar() {
print('$ad miyavladı: Miyav!');
}
}

void main() {
// İşte Polymorphism'in sihri!
// Liste, "Hayvan" türünde. Ama içine Kopek ve Kedi koyabiliyoruz.
// Çünkü Kopek bir Hayvan'dır (IS-A) ve Kedi bir Hayvan'dır (IS-A).
List<Hayvan> hayvanlar = [];
hayvanlar.add(Kopek('Karabaş'));
hayvanlar.add(Kedi('Pamuk'));
hayvanlar.add(Kopek('Fındık'));

// Şimdi tüm hayvanları tek bir döngüde "konuşturalım".
// 'hayvan' değişkeni her döngüde farklı bir forma bürünüyor.
// Ama biz hep aynı metodu çağırıyoruz: sesCikar()
for (var hayvan in hayvanlar) {
// Program, 'hayvan' değişkeninin o anki gerçek tipinin (Kopek mi, Kedi mi)
// override edilmiş metodunu akıllıca bulur ve çalıştırır.
hayvan.sesCikar();
}
}
// Çıktı
// Karabaş havladı: Hav hav!
// Pamuk miyavladı: Miyav!
// Fındık havladı: Hav hav!

Kod üzerinden de anlatmaya çalıştım. Eğer karmaşık geliyorsa lütfen acele ve pes etmeyin. Seride sık sık söylediğim gibi hemen anlaşılmasını beklemeyin. Bunlar birer basamak ve bu basamakları pratikle, zamanla aşacaksınız. Endişeniz olmasın. Yeterki isteğinizi, azminizi ve merakınızı kaybetmeyin!

Polymorphism’in Faydaları:

  1. Esneklik: hayvanlar listesine yarın bir Kus sınıfı eklersek, main fonksiyonundaki döngüyü hiç değiştirmemiz gerekmez. Sadece Kus sınıfının kendi sesCikar() metodunu (cik cik!) yazmamız yeterlidir. Kodumuz yeni türlere kolayca adapte olur.
  2. Temiz Kod: Farklı tipler için if (hayvan is Kopek) veya if (hayvan is Kedi) gibi kontrollerle dolu if/else blokları yazmaktan kurtuluruz. Nesnelerin kendi davranışlarından sorumlu olmasını sağlarız. Bu, kodun bakımını ve okunabilirliğini muazzam ölçüde artırır.

Son olarak dikkat ettiyseniz hayvan sınıfını tanımlarken başına abstract kelimesini ekledim acaba neden? Bu sorumuzun yanıtını serimizin diğer yazısında veriyor olacağım.

Serimizi takip etmeyi unutmayın.

Umarım ilgilisi ve meraklısı için faydalı olur.

Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow:
https://stackoverflow.com/users/13807726/abdullah-t

--

--

AbdullahTaş
AbdullahTaş

Written by AbdullahTaş

FLUTTER DEVELOPER AT IWWOMI | HASURA-GRAPHQL & FIREBASE |

No responses yet