JavaScript ile Nesne Yönelimli Programlama


10 Mart 2013     Etiketler: JavaScript NesneYonelimliProgramlama JSON Prototype

JavaScript ve nesne yönelimli programlama prensipleriyle nasıl modüler, yeniden kullanılabilir ve sürdürülebilir JavaScript yazılımları üretebiliriz bunu inceliyoruz.


Bu makale F5 Dergi Kasim-Aralik 2012 sayisinda yayinlanmistir.


JavaScript ile Nesne Yönelimli Programlama


İnternet ve web teknolojilerinin günlük yaşamın bir parçası olmaya başladığı son yıllarda, akıllı telefon ve tablet gibi taşınabilir cihazların artan popülaritesine de bağlı olarak JavaScript programlama dili web tabanlı çözümlerin bir vazgeçilmezi halindedir artık. Apple firmasının iOS işletim sistemiyle başlattığı “eklentisiz web” dalgası ile Flash ve Silverlight gibi eklenti (plugin) programlara bağımlı halde çalışan web çözümlerinin yavaş yavaş gözden düşmesi ve HTML5-CSS3 teknolojilerinin giderek yaygınlaşmasıyla JavaScript günümüzde eskiye oranla daha sık ve ileri düzeyde kullanılmaktadır.


JavaScript artık web sayfalarındaki alert(“lütfen geçerli bir email adresi girin”) tarzı form doğrulama ya da if (n > 0) document.getElementById(“panel”).style.display = “none”; şeklinde basit HTML-CSS manipülasyonlarının ötesinde, veri işleyen ve oldukça karmaşık işlemler yapabilen bir web programlama dili haline gelmiştir.


Bu nedenlerden dolayıdır ki JavaScript dilini potansiyeline yakışır bir şekilde, düzenli ve verimli bir biçimde kullanabilmek gerekir. JavaScript ve nesne yönelimli programlama prensipleriyle nasıl modüler, yeniden kullanılabilir ve sürdürülebilir JavaScript yazılımları üretebiliriz bunu inceleyeceğimiz makalemize JavaScript diline has olan temel kavramları gözden geçirerek başlayalım.


Tip esnekliği


JavaScript klasik programlama dillerinin aksine esnek tiplerden oluşan bir dildir. Yani Java, C#, C++ vb. dillerde değişkenlerimizi tipleriyle birlikte tanımlamak ve değer atamalarını tiplere uygun olacak şekilde yapmak zorundayken JavaScript dilinde var kelimesiyle değişken tanımlayabilir ve istediğimiz tipte değerler atayabiliriz. Mesela aşağıdaki ifadeler JavaScript dilinde hayata sebep olmayacak ifadelerdir.

var değişken = 1; //değişken tanımı ve sayısal değer atama
değişken = "bir"; //aynı değişkene metinsel değer atama
if (değişken == 1 || değişken == "bir") //iki farklı tiple kıyaslama
değişken = new Date("1/1/2012"); //tarih değer atama

Dinamik bir dil


Tip konusunda esnek olmasıyla birlikte mevcut nesnelerin dinamik bir biçimde manipüle edilebilmesi, çalışma esnasında (runtime) bile alan ve metot gibi yapıların nesnelere dinamik olarak eklenebilmesi de JavaScript ortamında mümkündür. Bunu bir örnekle şekillendirmek sanırız anlamanın en iyi yolu olacaktır.

var nesne = new Object(1);//yeni nesne yarat ve değer olarak 1 ata
alert(nesne); //1
nesne.metinsel = "bir"; //nesne’ye dinamik olarak yeni bir //alan ekle ve bu alana değer ata
alert(nesne.metinsel); //dinamik olarak eklenen alanı oku: "bir"

Örneğimizde de görüldüğü gibi JavaScript ortamında new anahtar kelimesiyle yaratılan nesnelere dinamik olarak yeni alanlar ekleyebiliriz. Benzer şekilde mevcut nesnelere dinamik olarak yeni metotlar bile ekleyebiliriz.


//tekmil adlı bir metot ekleyelim
nesne.tekmil = function () { alert(this.metinsel) };

//eklenen fonksiyon içindeki this anahtar kelimesine dikkat
//bu bağlamda this nesnenin kendisini temsil etmekte
 nesne.tekmil(); //"bir"

Yukarıdaki kod parçasında nesne değişkenimize tekmil adlı bir fonksiyon ekliyoruz. Bu yeni fonksiyon içinde nesnenin kendisine this anahtar kelimesiyle referans ediyoruz. Bu kavram C# dilindeki this anahtar kelimesi ve VB.NET dilindeki Me anahtar kelimelerine benzer anlam ifade etmektedir.


Bu örneğimizle yavaş yavaş JavaScript derinliklerine doğru adım atmaya başlıyoruz. Nesneler üzerine dinamik olarak alan ve metotlar eklemek, ve içinde bulunulan bağlama göre this anahtar kelimesinin anlamı JavaScript dilinin önemli kavramlarındandır.


Sınıf yok! Nesne var!


JavaScript diyarında sınıf kavramı yoktur! Ancak sınıf kavramının olmaması nesne kavramının da olmadığı anlamına gelmiyor. JavaScript nesneleri birer anahtar-değer dizileri olarak düşünülebilirler. Yukarıda değindiğimiz dinamik alan ekleme veya JSON (JavaScript Object Notation) nesne belirtkeleri yöntemiyle nesneler oldukça basit bir şekilde yaratılabilirler.

//JSON formatında yaratılan bir nesne
var nesne1 = { ad: "F5 Dergi", url: "http://f5dergi.com" };
alert(nesne1.ad); //"F5 Dergi"


//JSON formatında yaratılan bir nesne dizisi
var dizi = [
             {ad: "Bing", url: "www.bing.com"},
             {ad: "Google", url: "www.google.com"},
             {ad: "Yahoo", url: "www.yahoo.com"}
           ];

alert(dizi[0].ad + ": " + nesneler[0].url); // Bing: www.bing.com


JSON ile yaratılan nesneler veri taşıma işlemlerinde sıklıkla kullanılan bir yöntemdir. JSON hatta XML’e kıyasla daha az yer kaplayacağından performans açısından XML’den daha çok tercih edilmektedir. Ancak JSON kullanmak bize gerçek anlamda bir nesne yönelimi sağlamaz. İşlev ve mantığı yeniden kullanılabilecek modüler yapılar içinde toplayabilmemiz için klasik dillerdeki sınıf kavramına benzer yapılar oluşturmalıyız. Ancak sınıf kavramının var olmadığı JavaScript dili ile bu nasıl olabilir?


Sınıf yok ama fonksiyon var!


JavaScript dilinin bir başka resmi özelliği fonksiyonları “birinci sınıf” statüsünde kabul etmesidir. Fonksiyonların birinci sınıf statüsünde olması kavramını biraz açalım.

Klasik nesne yönelimli programlama dillerinde fonksiyonlar, birinci sınıf değillerdir. Bu tip dillerde bir fonksiyon sadece ve sadece bir sınıf içinde yer alabilir ve bir parametre olarak bir sınıftan başka bir sınıfa aktarılamazlar (delege fonksiyonlar buna bir istisnadır). Yani fonksiyonlar tek başlarına var olamazlar. Bu yüzden birinci sınıf vatandaş değillerdir. Halbuki JavaScript dünyasında fonksiyonlar tek başlarına var olabilir ve diğer fonksiyonlar arasında parametre olarak gidip gelebilirler. JavaScript’in bu özelliklerinden dolayı fonksiyonlar birinci sınıf vatandaş olarak nitelendirilirler.


Şimdi JavaScript fonksiyonların sınıf kavramını nasıl simule edebileceğini ve bunun bize getirilerini inceleyelim . Personel adlı bir sınıf-fonksiyon tanımlayalım ve bunun klasik sınıf kavramından nasıl farklılıklar taşıdığını irdeleyelim.


function Personel(ad, soyad, pozisyon) {
  //Alanlar
   this.Ad = ad;
   this.Soyad = soyad;
   this.Pozisyon = pozisyon;

   //Metot
   this.BilgiVer = function () {
     alert(this.Ad + " " + this.Soyad + ". " + this.Pozisyon + " olarak çalışmaktayım.");
   };

  //Metot
  this.PozisyonDeğiştir = function (yeniPozisyon) {
    this.Pozisyon = yeniPozisyon;
  };
}


var kişi = new Personel("Ali", "Can", "Yazılımcı"); //yeni nesne
kişi.BilgiVer(); //Ali Can. Yazılımcı olarak çalışmaktayım.


kişi.PozisyonDeğiştir("Proje Müdürü");
kişi.BilgiVer(); //Ali Can. Proje Müdürü olarak çalışmaktayım.

Yukarıdaki örnekte Personel adlı bir sınıf-fonksiyon yazdık. Bu fonksiyon Ad, Soyad ve Pozisyon adlı umumi alanlarla birlikte BilgiVer ve PozisyonDeğiştir adlı metotları içinde barındırıyor. Bu tür sınıf-fonksiyonlar yapıcı (constructor) fonksiyon olarak adlandırılırlar.


JavaScript ortamında new Personel(…, …, …) gibi bir ifadenin tetiklediği olaylar şöyledir: Yeni ve boş bir nesne yaratılır ve bu nesneye Personel fonksiyonun içindeki this değeri atanır. (Sanki Personel fonksiyonu return this ifadesiyle bitiyormuşçasına… Personel fonksiyonu return this ifadesiyle bitmediği halde new anahtar kelimesini takip eden fonksiyonlara uygulanan bir özelliktir bu.)


Bu şekilde yaratılan nesneler üzerinden .Ad, .Soyad, .Pozisyon, .BilgiVer(), .PozisyonDeğiştir() alan ve metotlarına ulaşabiliriz. Yani sınıf olmayan JavaScript dilinde ilk sınıfımızı yazdık!


Burada özenle vurgulamamız gereken bir ayrıntı söz konusudur. Örneğimizdeki BilgiVer ve PozisyonDeğiştir metotlarının ana fonksiyon içinde tanımlanması pratikte kabul edilebilir olsa da, teoride yanlış bir yaklaşımdır. JavaScript nesnelerinin birer anahtar-değer koleksiyonları olarak düşünülebileceğinden az önce bahsettik. Şimdi yukarıdaki şekliyle yazılmış olan Personel yapıcı fonksiyonu aracılığıyla yaratılacak nesnelerin bellekte nasıl tutulacağını görelim.  Buradaki problem yaratılacak her bir nesne için BilgiVer ve PozisyonDeğiştir metotlarının bellekte tekrar ve tekrar yer işgal edecek olmasıdır. Bu durum klasik nesne yönelimli programlama dillerinde (Java, C# vs.) var olmayan bir problemdir.


Klasik nesne yönelimli programlama dillerinde tanımlanan metotlar içinde tanımlandıkları sınıf üzerinden kaç nesne yaratılırsa yaratılsın bellekte sadece bir defa yer işgal edeceklerdir. Yaratılan nesneler bu bellek adreslerine işaret eden ibreler (pointer) aracılığıyla bu metotlara ulaşabilirler. JavaScript dilinde bu sonucu elde edebilmemiz JavaScript’in prototype adıyla bilinen özelliğiyle mümkündür.


Prototype


JavaScript ortamında var olan her yapıcı fonksiyon prototype adında bir prototip alanına sahiptir. Bu alan bize yapılar arasındaki kalıt alma ilişkilerini düzenleme imkanı sağlar. JavaScript’e ait Object yapısı bağlamda tanımlamış bütün nesneler için kalıt zincirinin ilk halkasını oluşturur ve diğer bütün yapılar direkt veya dolaylı olarak bu Object yapısından kalıt alırlar.

Aşağıdaki kod parçası bize Personel yapısının prototipinin Object tipinde olduğunu gösterecektir.


alert(Personel.prototype); //[object Object]

Nesneler arasında paylaşılması gereken fonksiyonları direkt olarak yapıcı fonksiyonumuz içerisinde değil, .prototype özelliğine dinamik olarak eklenen fonksiyonlar olarak tanımlayarak yukarıda sözünü ettiğimiz bellek problemini ortadan kaldırabiliriz.


//Yapıcı (constructor) fonksiyon
function Personel(ad, soyad, pozisyon) {
   this.Ad = ad;
   this.Soyad = soyad;
   this.Pozisyon = pozisyon;
}


//BilgiVer metodunu Personel yapısının prototipine ekliyoruz
Personel.prototype.BilgiVer = function () {
   alert(this.Ad + " " + this.Soyad + ". " + this.Pozisyon + " olarak çalışmaktayım.");
};


//PozisyonDeğiştir metodunu Personel yapısının prototipine ekliyoruz
Personel.prototype.PozisyonDeğiştir = function (yeniPozisyon) {
   this.Pozisyon = yeniPozisyon;
};


//Kalıt ağacının kökündeki Object yapısı tarafından tanımlanan .toString metodunu yeniden yazıyoruz
Personel.prototype.toString = function () { return "Personel"; };

NOT: Personel yapımız içinde toString metodunu da tanımlıyoruz. Bu az sonra bu yapıdan kalıt alacak yapıları teşhis etmemizi kolaylaştıracak.


Şimdi Personel yapısı üzerinden yaratacağımız nesnelere ait BilgiVer ve PozisyonDeğiştir metotları bellekte yalnızca bir kere yer işgal edeceklerdir.


Peki şimdi Personel yapımızdan kalıt alacak başka bir yapı düşünelim: Yönetici. Her bir yönetici aynı zamanda bir personel olacağından Yönetici yapısını Personel yapısının bir alt sınıfı olarak yazalım.


function Yönetici(ad, soyad, pozisyon) {
  //üst sınıfın yapıcı (constructor) metodunu çağır  
  Personel.call(this, ad, soyad, pozisyon);
  this.Elemanlar = []; //boş elemanlar dizisi
}


//Personel yapısından kalıt al
Yönetici.prototype = new Personel();


//yeni ve sadece yöneticiye ait metot
Yönetici.prototype.ElemanEkle = function (personel) {
   this.Elemanlar.push(personel);
};

Yönetici ve Personel yapıları arasındaki alt sınıf-üst sınıf ilişkisini Yönetici yapısının prototype alanına yeni bir Personel nesnesi atayarak gerçekleştiriyoruz ki Personel yapısına ait olan Ad, Soyad, Pozisyon alanları ve BilgiVer, PozisyonDeğiştir metotları Yönetici tipinde yaratılacak nesneler üzerinden de ulaşılabilsin.


Örnek kullanım şekli olarak aşağıdaki kodu inceleyelim.

var yönetici = new Yönetici("Veli", "Ak", "Genel Müdür");
yönetici.BilgiVer(); //alt sınıf metodu

//yeni bir eleman yarat ve yöneticiye ekle
var eleman = new Personel("Ali", "Can", "Yazılımcı");

yönetici.ElemanEkle(eleman); //üst sınıf metodu

alert(Yönetici.prototype); //Yöneticinin prototipi Personel

Toparlamak gerekirse JavaScript’in prototype özelliği aracılığıyla gerçekleştirilebilecek iki konuyu inceledik:

  • Bellekte gereksiz yer işgal etmeyen sınıf-benzeri yapıların nasıl tanımlanabileceği
  • Kalıt alma


Ancak dikkat ederseniz prototip tekniği tanımladığımız yapıların modülerliğinin bozulmasına sebep oldu. Daha önce tek ve bağımsız bir fonksiyon içinde sınıf-benzeri yapılarımızı tanımlayabilirken, şimdi elimizde birbirine bağımlı ama ayrık yapılar var!


Modüler Teknik
function Personel(ad, soyad, pozisyon) {
  this.Ad = ad;
  this.Soyad = soyad;
  this.Pozisyon = pozisyon;
  this.BilgiVer = function () {...};
  ...
}

Modüler ancak bellek açısından etkin değil


Prototip Tekniği
function Personel(ad, soyad, pozisyon) {
  this.Ad = ad;
  this.Soyad = soyad;
  this.Pozisyon = pozisyon;
}

Personel.prototype.BilgiVer = function () {...};

Bellek açısından etkin ancak mo-düler değil
...


Peki yapılarımızı hem modüler hem de bellek kullanımı açısından etkin bir biçimde yazmak mümkün mü? Haziran-Temmuz 2012 sayımızda da incelediğimiz kendini çağıran JavaScript kapanımları bunu mümkün kılmaktadır. Yapıcı metot ve Personel.prototype alanına eklediğimiz metotları kendini çağıran bir fonksiyon içine kapatarak kaybettiğimiz modülerliği yeniden kazanabiliriz.


var Personel = (function() {    //kapanım fonksiyonu aç


   //yapıcı (constructor) metot
   function Personel(ad, soyad, pozisyon) {
     this.Ad = ad;
     this.Soyad = soyad;
     this.Pozisyon = pozisyon;
   }


   //BilgiVer metodunu Personel yapısının prototipine ekliyoruz
   Personel.prototype.BilgiVer = function () {
     alert(this.Ad + " " + this.Soyad + ". " + this.Pozisyon + " olarak çalışmaktayım.");
   };


   //PozisyonDeğiştir metodunu Personel yapısının prototipine ekliyoruz
   Personel.prototype.PozisyonDeğiştir = function (yeniPozisyon) {
     this.Pozisyon = yeniPozisyon;
   };


  //Kalıt ağacının kökündeki Object yapısı tarafından
  // tanımlanan .toString metodunu yeniden yazıyoruz
  Personel.prototype.toString = function () { return "Personel"; };


  return Personel; //yapıcı metodu döndür

} //kapanım fonksiyonunu kapat

)(); //kapanım fonksiyonu: kendini çağır


REFERANS:
F5 Makale: JavaScript Kapa-nımları ve Kendini Çağıran Fonksiyonlar (http://f5dergi.com/Makale/JavaScript-Kapanimlari-ve-Kendini-Cagiran-Fonksiyonlar-/14)


Personel yapımızı bu şekilde yazmak kullanıcı kod tarafında hiçbir değişikliğe sebep olmayacaktır:


var kişi = new Personel("Ali", "Can", "Yazılımcı");


Personel adlı yapımızı ve bu yapının prototype alanına eklediğimiz metot-ları kendini çağıran adsız bir kapanım fonksiyonu ile çevrelemek, ve bu fonksiyonun içinden yapıcı (constructor) fonksiyonu döndürmek (return Personel), ve son olarak da bu dönen referansı Personel adlı bir değişke-ne atamak suretiyle sınıf-benzeri yapımızı modüler bir şekilde tanımlamış oluyoruz.


Sınıf kavramının olmadığı JavaScript dili ile nesne yönelimli programlama konusunun temellerini inceledik. Son yıllarda JavaScript’in artan ve karma-şıklaşan kullanımına paralel olarak JavaScript kod üretimini kolaylaştırmak amacıyla birtakım aracı diller türemiştir. CoffeScript ve Microsoft tarafın-dan şu sıralar ön-izleme (preview) versiyonundaki TypeScript bunlara ör-nek dillerdir. Bu diller yeni kodlama imlaları tanımlasalar da derlenmiş hal-leri yine bildiğimiz JavaScript’in ta kendisidir. Bu durum JavaScript’in artan önemine net bir işarettir. Bu nedenle JavaScript'i iyi anlamak, potansiyelinin farkına varmak ve etkin kullanabilmek için kendimizi geliştirmek yazılımcılar için kaçınılmaz hale gelmektedir.




Yorumlar


Bu makaleye ait yorum bulunamadı.

Sizin yorumunuz

Email


Adınız







F5 Dergi © 2017