Dart Dilinde Nesne Tabanlı Programlama(OOP) — Abstract Class & Mixin Kavramları |5.Bölüm
Merhaba, değerli geliştirici arkadaşlar geçtiğimiz ilk 4seride şunları öğrendik;
Sınıf(Class), Kurucu/Yapıcı(Constructor), Kapsülleme(Encapsulation) ve Kalıtım(Inheritance).
Kaçıranlar ve ilk defa denk gelenler için serinin ilk 4 makalesine aşağıdaki bağlantılardan erişebilirsiniz;
1.DERS OLAN SINIFLAR İÇİN BURAYA TIKLAYIN
2.DERS OLAN KURUCU/YAPICILAR İÇİN BURAYA TIKLAYIN
Önceki yazımızda Kalıtım (Inheritance) konusunu ve onun bize nasıl kod tekrarından kaçınma ve hiyerarşik yapılar kurma imkanı verdiğini gördük. PremiumUser’ın bir User olduğunu (IS-A ilişkisi) ve bu sayede ortak kodları miras alabildiğini öğrendik.
Ancak yazılım geliştirme dünyası her zaman bu kadar basit hiyerarşilere sığmaz. Karşımıza iki temel sorun çıkar:
- Sorun 1: Kural Koyma İhtiyacı: Bazen bir ebeveyn sınıfın sadece “ortak kod” sağlamasını değil, aynı zamanda çocuklarına “uymak zorunda oldukları kuralları” dikte etmesini isteriz. Örneğin, her Sekil’in bir alanHesapla() metodu olmalıdır. Ama Sekil’in kendisi için bu metodun bir anlamı yoktur, çünkü genel bir “şeklin” alanı hesaplanamaz. Bu kuralı nasıl zorunlu kılacağız?
- Sorun 2: Çoklu Yetenek İhtiyacı: Dart’ta bir sınıf, sadece tek bir sınıftan extends edilebilir. Peki ya bir Ordek sınıfı hem bir Hayvan’dır, hem ucabilir hem de yuzebilir? Bu üç farklı davranışı tek bir kalıtım hiyerarşisiyle nasıl modelleyeceğiz?
İşte bu iki soruna çözüm olarak Dart bize iki muhteşem araç sunuyor: Abstract Class (Soyut Sınıf) ve Mixin.
1. Abstract Class
Sınıfları öğrenirken sınıfları kullanarak nesneler oluşturabildiğimizden bahsetmiştik. Bu nesneleri oluşturup özelliklerine değer atayıp metotlarını kullanabiliyorduk. Abstract sınıflar için farklı bir durum mevcut.
Abstract Class (Soyut Sınıf), doğrudan nesnesi oluşturulamayan, tek amacı diğer sınıflara kalıtım yoluyla rehberlik etmek ve bir sözleşme (contract) sunmak olan özel bir sınıf türüdür. Yani sınıfımızı tanımlarken Abstract yaparsak doğrudan o sınıfın bir nesnesini üretmemiz mümkün değil. Ayrıca bir kurucu/yapıcısıda(constructor) mevcut değildir!
Peki ne zaman Abstract Class kavramına ihtiyacımız var?
Bir grup ilişkili sınıfın paylaşması gereken ortak bir arayüz veya temel davranışlar olduğunda, ancak bu temel sınıfın tek başına bir anlamı olmadığında kullanılır.
Örnekle açıklayacak olursak şekilleri düşünelim. Şekillerin alan ve çevre hesaplamasının yapılmasını zorunlu tutmak istediğimizi varsayalım. Ancak şeklin kendisinin yani şekil sınıfının kendisinin alan ve çevre hesaplamasına ihtiyacı yoktur çünkü bu sınıfı miras alan şekillerin bunlara ihtiyacı olacaktır. Bizim şekil sınıfımız sadece rehberlik etmek için bulunuyor.
Kare extends Sekil dediğimizde zorunlu olarak alan ve kenar hesaplaması yapılmasını isteyecektir. Dikkat ederseniz şekil sınıfını miras alan Kare sınıfı için geçerli bu kural.
Çok uzatmadan bu klasik örneği kod üzerinden açıklayalım;
// SOYUT SINIF: Bir "Şekil"in ne olması gerektiğine dair bir sözleşme.
abstract class Sekil {
// Normal bir özellik, tüm çocuklar miras alır.
String renk;
Sekil(this.renk);
// Normal bir metot, tüm çocuklar miras alır.
void rengiYazdir() {
print('Bu şeklin rengi: $renk');
}
// SOYUT METOT: Gövdesi yok!
// Anlamı: "Ey benden türeyen sınıf! Bir alan hesaplamak ZORUNDASIN.
// Ama nasıl hesaplayacağın sana kalmış."
double alanHesapla();
// Bir soyut metot daha...
double cevreHesapla();
}
// SOMUT SINIF 1: Sözleşmeyi uyguluyor.
class Kare extends Sekil {
double kenar;
Kare(this.kenar, String renk) : super(renk);
@override
double alanHesapla() {
return kenar * kenar;
}
@override
double cevreHesapla() {
return 4 * kenar;
}
}
// SOMUT SINIF 2: Sözleşmeyi uyguluyor.
class Daire extends Sekil {
double yaricap;
Daire(this.yaricap, String renk) : super(renk);
@override
double alanHesapla() {
return 3.14 * yaricap * yaricap;
}
@override
double cevreHesapla() {
return 2 * 3.14 * yaricap;
}
}
void main() {
// HATA: 'Sekil' is abstract and can't be instantiated.
// var s = Sekil('Mavi');
var kirmiziKare = Kare(5, 'Kırmızı');
var maviDaire = Daire(3, 'Mavi');
print('Karenin alanı: ${kirmiziKare.alanHesapla()}'); // Çıktı: 25.0
kirmiziKare.rengiYazdir(); // Çıktı: Bu şeklin rengi: Kırmızı
print('Dairenin alanı: ${maviDaire.alanHesapla()}'); // Çıktı: 28.26
maviDaire.rengiYazdir(); // Çıktı: Bu şeklin rengi: Mavi
}
Eğer Kare sınıfında alanHesapla() metodunu yazmayı unutsaydık, Dart anında hata verirdi: “Kare is not a valid override of Sekil.alanHesapla”. Böylece, soyut sınıf sayesinde programatik bir zorunluluk oluşturmuş olduk.
Umarım yukarıdaki kod bu konuyu anlamanıza yardımcı olur. Abstract konusunu burada bırakıp bir sonraki konumuza geçiyoruz.
2. Mixin
Mixin adında anlaşıldığı gibi karıştırmak anlamına gelir. Bir sınıf hiyerarşisi oluşturmadan, bir veya daha fazla sınıfa metot ve özellik eklemenin bir yoludur. Kalıtım gibi “IS-A” (bir…dır) ilişkisi kurmaz. Daha çok “CAN-DO” (…-yapabilir) veya “HAS-A” (sahiptir) ilişkisi kurar. Sınıflara yeniden kullanılabilir davranışlar “karıştırmamızı” sağlar.
Kendi kendinize ne anladın yurdagül dediğinizi duyar gibiyim o yüzden daha basite indirgeyip açıklamaya çalışayım;
Bir oyun geliştirdiğimizi düşünelim. Oyunumuzda Savaşcı, Büyücü ve Şifacı olsun. Yani bu karakterlerin her biri bizim sınıfımız.
class Savasci {}
class Buyucu {}
class Sifaci {}
Bu karakterlerin gelişimi için karakter kısıtlaması olmadan çeşitli görev ya da yetenekler eklemek istiyoruz. Yani sadece tek bir sınıfa değil tüm sınıflara eklemek istiyoruz. İşte bu yetenekler bizim Mixinlerimiz olacak. Kod üzerinde göreceğiz ama önce nasıl kullanacağınız kısaca buna değineyim.
Nasıl Kullanılır?
- mixin anahtar kelimesi: Bir yetenek paketi oluşturmak için kullanılır. Mixin’lerin nesnesi oluşturulamaz.
- with anahtar kelimesi: Bir veya daha fazla mixin’i bir sınıfa “karıştırmak” için kullanılır.
Mixinler bizim tüm karakterlerimize ekleyeceğimiz yetenek kitabımız olacak.
1.Yetenek Kitabı: “Hayvanlarla Konuşma” (mixin HayvanDostu)
- Bu kitabı okuyan (with kullanan) herkes, hayvanlarlaKonus() yeteneğini kazanır.
2.Yetenek Kitabı: “Görünmezlik Pelerini” (mixin Gorunmezlik)
- Bu pelerini kuşanan (with kullanan) herkes, gorunmezOl() yeteneğini kazanır.
3.Yetenek Kitabı: “Demircilik Sanatı” (mixin Demirci)
- Bu kitabı okuyan (with kullanan) herkes, silahDov() yeteneğini kazanır.
Yetenek kitabımız hazır yani mixinlerimiz hazır peki karakterler üzerinde nasıl kullanacağız?
1. Savaşçımız, demircilik öğrenmek istiyor.
- Kod: class Savasci extends Karakter with Demirci {}
- Anlamı: Savaşçı, temel bir Karakter’dir ve Demircilik Sanatı kitabını okumuştur. Artık hem saldir() hem de silahDov() yapabilir.
2. Okçumuz, ormanda yaşadığı için hayvanlarla dost olmak istiyor.
- Kod: class Okcu extends Karakter with HayvanDostu {}
- Anlamı: Okçu, temel bir Karakter’dir ve Hayvanlarla Konuşma kitabını okumuştur. Artık hem okAt() hem de hayvanlarlaKonus() yapabilir.
3. Büyücümüz ise gizemli bir karakter. Hem görünmez olmak hem de hayvanlarla konuşmak istiyor.
- Kod: class Buyucu extends Karakter with Gorunmezlik, HayvanDostu {}
- Anlamı: Büyücü, temel bir Karakter’dir ve hem Görünmezlik Pelerini’ni kuşanmış hem de Hayvanlarla Konuşma kitabını okumuştur. Artık hem buyuYap(), hem gorunmezOl() hem de hayvanlarlaKonus() yapabilir.
//-----------------------------------------------------
// 1. TEMEL KARAKTER SINIFI (KALITIM İÇİN)
//-----------------------------------------------------
// Tüm karakterlerin ortak özelliklerini taşıyan temel sınıf.
class Karakter {
String ad;
int can;
Karakter(this.ad, this.can);
void tanit() {
print('Ben $ad, canım: $can');
}
}
//-----------------------------------------------------
// 2. YETENEK KİTAPLARI (MİXİN'LERİMİZ)
//-----------------------------------------------------
// Hayvanlarla Konuşma yeteneğini içeren "kitap".
mixin HayvanDostu {
void hayvanlarlaKonus() {
// 'ad' özelliğini kullanabiliyor çünkü bu mixin'in 'Karakter' veya
// ondan türeyen bir sınıfla kullanılacağını varsayıyoruz.
// Güvenli kullanım için 'on Karakter' eklenebilir ama basitlik için eklemedik.
print('(HayvanDostu) Bir sincapla fındık pazarlığı yapıyorum!');
}
}
// Görünmezlik yeteneğini içeren "pelerin".
mixin Gorunmezlik {
void gorunmezOl() {
print('(Görünmezlik) Bir duman bulutu içinde kayboldum...');
}
}
// Demircilik yeteneğini içeren "kitap".
mixin Demirci {
void silahDov() {
print('(Demirci) Örsün başında alev alev bir kılıç dövüyorum!');
}
}
//-----------------------------------------------------
// 3. KARAKTER SINIFLARIMIZ (KALITIM ve MİXİN'LERİN BİRLEŞİMİ)
//-----------------------------------------------------
// Savaşçı, bir Karakter'dir ve Demircilik yeteneğine SAHİPTİR.
class Savasci extends Karakter with Demirci {
Savasci(String ad) : super(ad, 150); // Savaşçının canı daha yüksek.
void saldir() {
print('$ad, güçlü bir kılıç darbesi vurdu!');
}
}
// Okçu, bir Karakter'dir ve HayvanDostu yeteneğine SAHİPTİR.
class Okcu extends Karakter with HayvanDostu {
Okcu(String ad) : super(ad, 100);
void okAt() {
print('$ad, hedefini tam onikiden vurdu!');
}
}
// Büyücü, bir Karakter'dir ve hem Görünmezlik hem de HayvanDostu yeteneklerine SAHİPTİR.
class Buyucu extends Karakter with Gorunmezlik, HayvanDostu {
Buyucu(String ad) : super(ad, 80); // Büyücünün canı daha az.
void buyuYap() {
print('$ad, bir ateş topu fırlattı!');
}
}
//-----------------------------------------------------
// 4. OYUNU BAŞLATALIM! (ANA FONKSİYON)
//-----------------------------------------------------
void main() {
// Karakterlerimizi oluşturalım.
var aragorn = Savasci('Aragorn');
var legolas = Okcu('Legolas');
var gandalf = Buyucu('Gandalf');
print('--- Savaşçı Sahnede ---');
aragorn.tanit(); // Karakter'den gelen temel özellik.
aragorn.saldir(); // Savasci'nın kendi yeteneği.
aragorn.silahDov(); // Demirci mixin'inden gelen YETENEK.
print('\n--- Okçu Sahnede ---');
legolas.tanit(); // Karakter'den gelen temel özellik.
legolas.okAt(); // Okcu'nun kendi yeteneği.
legolas.hayvanlarlaKonus(); // HayvanDostu mixin'inden gelen YETENEK.
print('\n--- Büyücü Sahnede ---');
gandalf.tanit(); // Karakter'den gelen temel özellik.
gandalf.buyuYap(); // Buyucu'nun kendi yeteneği.
gandalf.gorunmezOl(); // Gorunmezlik mixin'inden gelen YETENEK.
gandalf.hayvanlarlaKonus(); // HayvanDostu mixin'inden gelen YETENEK.
// Hatalı kullanımlar (yorum satırını kaldırırsanız hata verir):
// aragorn.gorunmezOl(); // Savaşçı bu yeteneği öğrenmedi!
// legolas.silahDov(); // Okçu demircilik bilmiyor!
}
- extends Karakter: Bu kısım, her sınıfın kimliğini belirler. Savasci bir Karakterdir. Bu, tanit() gibi temel metotları miras almasını sağlar.
- with Demirci: Bu kısım, Savasci’nın kimliğini değiştirmeden ona ek bir yetenek katar. Savasci hala bir savaşçıdır ama artık demircilik de yapabilir.
- with Gorunmezlik, HayvanDostu: Buyucu sınıfı, virgülle ayırarak birden fazla “yetenek kitabını” aynı anda kullanabilir.
- Çağrılar: main fonksiyonunda her nesnenin hem kendi sınıfından, hem Karakter ebeveyninden, hem de dahil edildiği mixin’lerden gelen metotları nasıl rahatça çağırabildiğini görebiliriz.
Umarım bu kod örneği anlamanıza yardımcı olur!
Şimdi burada aklınıza şu soru gelmiş olabilir extends varken neden mixin kullanma gereği duyalım?
extends kelimesi, sınıflar arasında güçlü, hiyerarşik bir “IS-A” (bir…dır) ilişkisi kurar. Bu, bir sınıfın temel kimliğini ve ailesini tanımlar.
- Bir Kopek bir Hayvan’dır.
- Bir Savasci bir Karakter’dir.
- Bir Mercedes bir Araba’dır.
Ve extends kavramının en büyük sınırı sadece bir aileye sahip olabilirsin yani bir sınıfın sadece bir ebeveyni olabilir.
Bir Kus sınıfımız var. Kus, bir Hayvan’dır. O zaman kodumuz:
class Kus extends Hayvan {} // Bu doğru.
Ama Kuş, aynı zamanda uçma davranışına da sahiptir. Eğer “uçma davranışını” Ucucu adında bir sınıfta toplarsak ne olur?
class Kus extends Hayvan, extends Ucucu {}
// -> BU KOD GEÇERSİZ! Dart buna izin vermez. Bir sınıfın iki ebeveyni olamaz.
İşte extends burada tıkanır. Bir sınıfın temel kimliğini belirlemede mükemmeldir, ancak ona birden fazla, birbirinden bağımsız “yetenek” katmakta yetersiz kalır.
mixin, bu sorunu çözmek için vardır. mixin, bir sınıfın kimliğini (IS-A) değil, onun ne yapabildiğini (CAN-DO) tanımlar. Bir sınıfa miras hiyerarşisini bozmadan, dışarıdan “süper güçler” veya “yetenekler” ekler.
- Bir Kus uçma yeteneğine sahiptir.
- Bir Insan konuşma yeteneğine sahiptir.
- Bir Balik yüzme yeteneğine sahiptir.
- Bir Ordek ise hem uçma hem de yüzme yeteneğine sahiptir.
Sonuç;
class Ordek extends Hayvan with Ucucu, Yuzucu {}
Bu kod bize şunu söyler:
“Ördek, kimlik olarak bir Hayvan’dır, ama aynı zamanda Uçucu ve Yüzücü yeteneklerine de sahiptir.”
Evet 5.bölümün sonuna geldik. Abstract Class & Mixin Kavramlarını umarım sizler için anlaşılır kılmıştır bu yazı.
Buraya kadar okuyan ve destek olan herkese çok teşekkür ederim. Umarım ilgilisi ve meraklısı için faydalı olur.
Serimiz burada final yapıyor değerli arkadaşlar. Umarım sizler için faydalı bir seri olmuştur.
Github: www.github.com/abdullah017
Linkedin: www.linkedin.com/in/abdullahtas
Stackoverflow: https://stackoverflow.com/users/13807726/abdullah-t