Oca.20

Hızlandırılmış EcmaScript 6+ Dersleri – 11: Class (Sınıf) Yapısı ile Çalışmak


JavaScript, oldukça dinamik bir yapıya sahiptir. EcmaScript 6 öncesinde JavaScript’te Nesne Tabanlı Programlama yönelimi ile programlama yapmak biraz zahmetli bir işti. Aslında direkt olarak NYP (OOP) desteklemez, fakat Class yerine Object’ler oluşturmak ve bunları prototipleyerek kullanarak bir şekilde işimizi görüyorduk. Microsoft tarafında geliştirilen TypeScript ile de nesne yönelimli programlama daha güçlü bir hale gelmişti. Aşağıdaki EcmaScript 6 öncesinde prototipleme metodunun kullanımını ve aynı işlevi yapan Class’ı inceleyin.

Klasik Prototipleme Yöntemi

Örneğimizi sürüngenler üzerinden yapalım…

var Surungen = new Object() {
   // ...
}

Yukarıda Object olarak Surungen nesnesini tanımladık. Veya fonksiyon olarak da tanımlayabiliriz.

function Surungen() {
   // ...
}

JavaScript dilinin esnek yapısından dolayı bir Object’in özellikleri veya metotlarının yapısı değiştirilebilir. Bu yapı başta çok güzel gibi görünse de çok fazla serbest kalmak, bir süre sonra çok fazla karmaşıklığa yol açabiliyordu. Hatta sırf bu özelliğinden dolayı çoğu yazılımcı JavaScript’i çok enteresan ve ne zaman ne yapacağı belli olmayan bir dil olarak algıladı. Bütün JavaScript nesneleri, kök olarak Object’ten türetilir.

A şağıda, Surungen fonksiyonumuza isim ve yuzebilirmi şeklinde iki parametre verdik. Fonksiyon içinde yer alan this deyimi, oluşturulan nesneyi ifade eder.

var Surungen = function(isim, yuzebilirmi) {
  this.isim = isim;
  this.yuzebilirmi = yuzebilirmi;
}

Şimdi bu fonksiyonu prototipleyerek kullanalım…

Bu sefer Surungen nesnesine bogulabilirmi seklinde bir metot ekleyelim.

Surungen.prototype.bogulabilirmi = function() {
  if (this.yuzebilirmi) {
    console.log(`${this.isim} yüzebilir.`);
  } else {
    console.log(`${this.isim} boğulabilir.`);
  }
};

Evet, artık Surungen nesnesi için özellikler tanımlanabilir.

Örnek olarak ördeğin yüzüp yüzemeyeceğini düşünelim…

let ordek = new Surungen("Ördek", true);
ordek.bogulabilirmi(); // Ördek yüzebilir.

Konsolda, “Ördek yüzebilir” yazacaktır. Çünkü true değeri verdiğimiz için if koşuluna girecektir. Bir de kedi için deneyelim.

let kedi = new Surungen("Kedi", false); 
kedi.bogulabilirmi(); // Kedi boğulabilir.

Kedi için de false değerini verdik ve “Kedi boğulabilir” yazdırıldı.

Class Oluşturma ve constructor (Yapıcı) ve Metotlar

İlk olarak basit bir örnek ile Class oluşturmanın temellerini ve sözdizimini görelim.

class Hayvan {
    constructor(isim) {
        this.isim = isim;
    }
    kendiniTanit() {
        console.log(`Merhaba, bana ${this.isim} derler.`);
    }
}
 
let aslan = new Hayvan("Aslan");
aslan.kendiniTanit();

Örnek kodumuzu inceleyelim…

Bir Class oluşturmak için class clasIsmi {} yapısını kullanırız. Class ismi olarak Hayvan adını verdik. Yazım kuralı olarak Class isimleri mutlaka büyük harfle başlar. Aslında programatik bir zorunluluk değildir, ancak değişken isimleriyle karışmaması ve ilk bakışta bunun bir Class olduğu anlaşılması adına büyük harfle başlar.

Class içinde constructor metodu kullandık. Bu zorunlu değildir ve eğer kullanılacaksa bir Class’ta sadece bir tane constructor (yapılandırıcı) metot kullanılır. Constructor, metoda gelen parametreleri karşılar.

Ardından kendiniTanit adında bir metot oluşturduk. Bu metoda dışarıdan erişebileceğiz. Metot, Class çağırılırken verilen parametre değeri ile konsolda bir selamlama mesajı yazdırmaktadır.

Hazırlanan Class’ı new deyimi ile çağırıyoruz ve varsa parametrelerimizi Class’a ekliyoruz. Sonrasında da bu yeni oluşturulan Class örneğini aslan isminde bir değişkene aktardık. Artık değişken ismi ile örnek Class’a erişip kendiniTanit() metodunu çağırdık.

Örnek olarak hayvan ismini Aslan verdiğimizde aşağıdaki gibi konsolda yazdırılır.

Merhaba, bana Aslan derler.

İsimsiz Class Tanımlama

Daha önceki örneklerimizde gördük ama üzerinden geçmekte ve farkı görmekte fayda var.

Bir Class oluştururken önceki örneğimizde olduğu gibi bir isim verebiliriz. Veya doğrudan bir değişkene Class ataması da yapabiliriz.

Yukarıdaki örneğimizde Hayvan isminde bir Class tanımlamıştık. Şimdi isimsiz farklı bir Class oluşturalım.

const Daire = class {
    constructor(r) {
        this.PI = 3.14;
        this.r = r;
    }
    alanHesapla() {
        let alan = this.PI * this.r * this.r;
        console.log(`Dairenin alanı: ${alan} olarak hesaplandı.`);
    }
}
new Daire(9).alanHesapla();

Örneğimizde Daire isminde bir değişken (aslında Class olacağını bildiğimiz için büyük harfle ilk harfini yazdık, normalde değişken isimleri camelCase olarak yazılır) tanımladık.

Dikkat etmeniz gereken kritik bir nokta da; normal Class yapısından farklı olarak isimsiz oluşturulan Class’ı tanımlarken küçük harfle class şeklinde türünü belirttik. Diğer bir fark da, bu class’ı örneklerken bir değişkene aktarmadık ve direkt class’a parametre değerini verip, ardından zincir metot kullanım yöntemiyle doğrudan metodu çağırdık (jQuery’de zincir metot kullanımını hatırlayın).

Aldığı yarıçap değeri ile alanHesapla metodunu kullanarak dairenin alanını hesaplayıp konsolda yazdırdı. Örnek olarak yarıçap (r) değeri 9 olan bir daire için:

Dairenin alanı: 254.34 olarak hesaplandı.

Statik Metot Oluşturma

Statik metotlar, ES6’da gelen diğer özelliklerden biridir ve Nesne Yönelimli Programlamada Class’lara özgü metotlar tanımlanabilmesini sağlar.

Statik metotlar, Class başlatılmadan önce çağırılır ve daha sonrasında çalıştırılamaz. Bu tür metotlar Class’ın metotları gibi çağırılamadığından direkt olarak sınıfta çağırılır.

Genellikle uygulama içi bazı yardımcı işlemleri oluşturmak için kullanılır. Class içerisindeki değişkenlere erişemez. Class içindeki bir metodu statik yapabilmek için metodun başına static deyimini eklemek yeterlidir.

clasIsmi.metotIsmi()

Bu yöntem Class ismi ile doğrudan çağırmayı sağlar ve statik değeri verir. Statik metot sadece Class’ın kendisi tarafından çağırıldığı için new classIsmi() ile yeni bir örneğini oluşturmaya gerek kalmaz (zaten hata verir), çünkü zaten hangi Class’a ait olduğu bellidir.

Şimdi de örnekle inceleyelim…

class Zaman {
    static zamaniVer() {
       return new Date().toDateString();
    }
}
let simdi = Zaman.zamaniVer();
console.log(simdi);

Örneğimizde Zaman isimli Class’ın statik metodu olan zamaniVer metodunu doğrudan çağırdık. Bu metot da sistemin anlık olarak zamanını okunabilir (yani sistemin dil ayarına göre düz metin olarak formatlanmış tarih bilgisi) bir şekilde return etti. Gelen değeri de simdi isimli bir değişkene aktarıp konsolda yazdırdık. Sonuç aşağıdaki gibi olacaktır.

Fri Aug 30 2019

Eğer bir Class örneği yapıp metoda erişmeye çalışırsak “bu bir fonksiyon değildir” hatası verir.

// Hata verir.
let zamanNesneOrnegi = new Zaman ();
console.log(  zamanNesneOrnegi.zamaniVer() );

Kalıtım (Inheritance) ve Class Genişletme (extends)

Class’larla çalışırken tek bir Class ile çalışabileceğimiz gibi birden fazla Class oluşturarak da çalışabiliriz. Veya bir kök Class kullanarak bu Class’ı genişleterek, yani yeni özellikler ekleyerek de ilerleyebiliriz.

Örneğin en basitinden yine Tarih isminde Class’ımız olsun. Bu Class bize temelde gün, ay ve yıl bilgilerini ayrı ayrı metotlarda versin. Ancak biz bu Class’ın yapısını bozmadan saat, dakika ve saniye bilgisini de almak istersek iki farklı yol izleyebiliriz. Ya Tarih Class’ını elden geçirip saat, dakika ve saniye bilgilerini de veren metotlar ekleyeceğiz, ya da Tarih Class’ını farklı bir Class ile-(mesela Zaman Class’ı ile) genişleteceğiz.

Bir Class’ı genişletmek için extend deyimi kullanılır. Yukarıda sözde bahsetmiş olduğumuz örneği gerçeğe çevirelim.

tarih.js

class Tarih {
    constructor(){
        this.zaman = new Date();
    }
    gun(){
        return this.zaman.getUTCDate();
    }
    ay(){
        return this.zaman.getMonth();
    }
    yil(){
        return this.zaman.getFullYear();
    }
}

Class’ımız oldukça basit. Constructor içinde this.zaman ile sistemin o anki zaman bilgisini new Date() ile alıyoruz. Metotlarımız içerisinde de sırasıyla günü, aynı ve yılı metotlarımız ile veriyoruz.

Ancak gördüğünüz üzere bu Class bize saat, dakika ve saniyeyi vermiyor. Bu Class’ı hiç bozmadan genişletip istediğimiz yeteneğe getireceğiz.

Yukarıda yazdığımız JavaScript’i tarih.js ismi ile kaydedelim. Yeni bir JavaScript dosyası daha oluşturup index.js olarak kaydedelim. Ana index.html dosyamızdan her iki js dosyasını da içe çekeceğiz.

index.js

class Zaman extends Tarih {
    saat(){
        return this.zaman.getHours();
    }
    dakika(){
        return this.zaman.getMinutes();
    }
    saniye(){
        return this.zaman.getSeconds();
    }
}
setInterval( () => {
    let zaman = new Zaman();
    let dijitalSaat = `${zaman.gun()}/${zaman.ay()}/${zaman.yil()} ${zaman.saat()}:${zaman.dakika()}:${zaman.saniye()}`;
    console.log( dijitalSaat );
}, 1000 ); 

index.js dosyasında görüldüğü üzere extends deyimi ile Tarih Class’ını genişletip yeni Class’a da Zaman adını verip içinde metotlarımızı ekledik. Dikkat edin, new Date() ile sistemin anlık zaman bilgisini alan zaman nesnesini yeniden oluşturmadık, çünkü Parent, yani üst Class’ımız olan Tarih’ten Zaman’a kalıtım yoluyla hem değişkenler hem de metotlar aktarıldı.

Son olarak setInterval() ile 1000 milisaniyede bir yeni bir Zaman nesnesi oluşturup zaman değişkenine aktardık ve dijitalSaat değişkenine String olarak tüm metotlarımızdan gelen değerleri aktarıp zamanı formatlı bir biçimde gösterdik. Sonucu da konsolda yazdırdık.

30/7/2019 19:36:50

30/7/2019 19:36:51

30/7/2019 19:36:52

…Unutmayın, extend edilecek yani kalıtım alınacak olan Class’a ait olan JavaScript dosyası, kalıtımı almak isteyen Class’tan önce yüklenmelidir. Kısaca; Parent olan Class’a ait script dosyası import edilirken üst sırada yazılmalıdır.

Metot Ezme (Override)

Bir Class’ı genişlettiğinizde tüm metotlar ve özellikler alt Class’a geçirilir. Ancak alınan bir metoda farklı bir davranış uygulamak istediğimizde ne yapmamız gerekir? Yapmamız gereken tek şey, alt Class’taki metodu yeniden tanımlamak olacaktır. Buna da metot ezme denir. Parent olan, yani kalıtımı alınacak olan Class’ta bir metodu ezip yerine Child olan, yani kalıtımı alan Class’tan yeni metot koyulabilir. Yani Child olan Parent’ın metotlarını ezip yerine geçebilir.

class Arac {
    yakit() {
        return 'Benzin kullanılır.';
    }
}
class Tesla extends Arac {
    yakit() {
        return 'Tesla araçları benzin kullanır.';
}
}
let araba = new Tesla();
console.log( araba.yakit() );

Örneğimizde Arac isminde bir Class oluşturup içinde de yakit isminde bir metot tanımladık. Bu metot, “Benzin kullanır” şeklinde bir String return ediyor. Sonrasında da Arac isimli Class genişletilerek Tesla adında başka bir Class’a evrildi. Yine yakit isminde aynı metodu kullanarak kalıtım aldığı metodu ezdi ve “Tesla araçları benzin kullanır” şeklinde bir metin return etti.

Yukarıdaki kodumuzu çalıştırdığımızda konsolda “Tesla araçları benzin kullanır” yazacaktır.

super() ile Parent Class’a Parametre Geçirmek

Bazı hallerde kalıtım alınan Class tarafından Parent olan Class’ın kurucu metodu (constructor) tekrar yeni parametre değerleri ile çalıştırılmak istenebilir. Bu gibi durumlarda super() metodu, kalıtım alınan Class’ın constructor’une genişletilmiş olan alt Class’tan parametre geçirmek için kullanılır.

Eler genişletilmiş olan alt Class’ın constructor metodu varsa, super() kullanımı zorunludur. Aynı şekilde alt Class’ın metotları içerisinde de super kullanımı ile tüm metot ve değişkenlere erişebiliriz.

Bir önceki örneğimizi biraz geliştirerek devam edelim.

class Arac {
    yakit() {
        return 'benzin';
    }
}
class Hibrid extends Arac {
    yakit() {
        return `Hibrid araçlar ${super.yakit()} ve elektrik kullanır.`;
    }
}
let araba = new Hibrid();
console.log( araba.yakit() );
// Hibrid araçlar benzin ve elektrik kullanır.

Bu örneğimizde Parent Class’ın metodunu super ile kullandık. Şimdi de constructor metotlarla parametre aktarmayı görelim…

class Calisan {
    constructor(isim, soyisim){
        this.isim = isim;
        this.soyisim = soyisim;
    }
 
    bildir(){
        return `${this.isim} ${this.soyisim} işe başlamıştır.`;
    }
}
class Gorev extends Calisan {
    constructor(isim, soyisim, yas, gorev){
        super(isim, soyisim);
        this.yas = yas;
        this.gorev = gorev;
    }
    goreviTanimla(){
        return `${this.isim} ${this.soyisim} ${this.yas} yaşındadır ve ${this.gorev} görevi ile işe başlamıştır`;
    }
}
let yeniGorevli = new Gorev("Uğur", "Gelişken", "36", "Programcı");
console.log( yeniGorevli.bildir() );
// Uğur Gelişken programcı işe başlamıştır. 
console.log( yeniGorevli.goreviTanimla() );
// Uğur Gelişken 36 yaşındadır ve Programcı görevi ile işe başlamıştır

Örneğimizde Calisan isimli bir Class tanımladık. Bu Class constructor metoduna isim ve soyisim şeklinde iki parametre alıp bildir isimli metodunda kullanım bir metin olarak yazdırıyor.

Ardından Calisan metodu işimizi görmemiş ki bunu extend edip Gorev isimli bir Class’a evriltmişiz. Gorev Class’ında da constructor metodunda isim, soyisim, yas ve gorev isimleri ile parametreler alınmış. Dikkat edin; isim ve soyisim parametreleri Calisan ve Gorev Class’larının constructor’lerinde ortak olarak kullanılmış. Bu nedenle super() metoduna isim ve soyisim parametreleri girilmiş ve Parent Class’ın constructor’une erişilmiş. Böylece this atamaları yapılarak Class’a dahil edilmiş. Böylece tekrar isim ve soyisim tanıtılmaya gerek kalmamış.

Calisan Class’ında da goreviTanimla metodu ile bu dört parametre değeri birleştirilerek metin olarak return edilmiş.

Class’ları kullanım şeklimize bakarsak; new Gorev() ile Gorev isimli alt Class’ın örneği alınıp yeniGorevli isimli değişkene aktarılmış Parametre olarak da sırasıyla o görevlinin ismi, soyismi, yaşı ve görevi belirtilmiş.

Konsol’da iki farklı yazdırma uygulanmış. Birincisinde bildir metodu çağırılmış. Bu metot Parent olan Calisan Class’ına ait ve o metotta sadece çalışanın ismi ve soyismi yazdırılıyor. İkinci kullanımda da goreviTanimla metodu çağırılmış. Bu da Child olan Gorev Class’ına ait. O metot da çalışanı tanıtarak yaşı ve görevini de yazdırmıştır.

Makale serimizin diğer konuları şunlar olacaktır:


Web Tasarımı ve Web Programlama 2020

Yorum bırak

Yorum