"Enter"a basıp içeriğe geçin

Çok Biçimlilik

Çok Biçimlilik (polymorphism)

Kalıtım konusunda da bahsettiğimiz gibi, aynı sınıftan türeyen sınıfların aynı adda, benzer işlevde ama birebir aynı olmayan fonksiyonları olabiliyordu. Yada ana sınıfta olan bir fonksiyon alt sınıfta tekrar tanımlandığında onu eziyordu. Bunlar isim olarak aynı ama özelleşmiş fonksiyonlardır. Çok biçimlilik olabilmesi için imzaları aynı olmalıdır. Yoksa operatör aşırı yükleme olur.

Mesela sekil sınıfını hatırlayalım. hem dikdörtgen hem de kare sınıfında print fonksiyonu vardı. dikdörtgen sınıfından gelen fonksiyon ihtiyacımızı karşılamadığından, bunu tekrar tanımlamıştık.

Birbiriyle kalıtımsal ilişkisi olan sınıflarda aynı işlevi her sınıfın kendi özelinde farklı şekillerde tanımlamaya çok biçimlilik (polymorphism) denir. Aşağıdaki örnek ile hatırlayalım.

Bu örnekte nesnenin üye fonksiyonuna erişme yöntemi ile, her sınıfın kendine özel fonksiyonlarına erişmeyi sağladık. Derleyici, nesnenin hangi sınıftan olduğunu bildiği için doğru fonksiyonu çağırabildi.

 

Şimdi main kısmını biraz değiştirelim.

Pointer ile erişim

Daha önceden bir  this-> pointer’ı görmüştük. Bu pointer, bir üye fonksiyonun içinde aynı adda 2 değişken varsa bu değişkenlerden üye değişken olanı kullanmayı sağlıyordu.
Aslında burada yaptığımız şey, pointer kullanarak bir nesneye erişmektir. Yani this bizim nesnemizi gösteren bir pointer’dır ve -> işareti de o pointerda tutulan nesnenin üyelerine erişmemizi sağlar.
Sınıfın içinde this->taban dersek, pointer’ın tuttuğu nesneye (bu durumda içinde bulunduğu sınıfın kendisi) ait taban üyesine erişiriz.

Bu mantığı sınıfın dışında da kullanabiliriz. Yani ptr->print(); dersek ptr pointerının tuttuğu nesne her neyse, o nesneye ait print fonksiyonunu çağırmış oluruz.

Yukarıdaki kodun main kısmını pointer ile erişilecek şekilde düzenleyelim:

Görüldüğü gibi, önceki kod ile aynı çıktıyı verdi. Yani pointer kullanarak da nesnenin üyelerine erişebiliriz.

 

Peki bu bizim ne işimize yarayacak? Kalıtımsal ilişkisi olan sınıflarda, ata sınıfın özellikleri ortak özellikti. Bu yüzden ata sınıfın pointer’ı ile diğer sınıfların nesnelerini tutabiliriz. Tabiki sadece ortak özellikleri tutarız.

Çok biçimlilik ne işimize yarar

Mesela dörtgen sınıfı düşünelim. Bir print fonksiyonu var. Biz tüm dörtgen tiplerini dörtgen pointer’ı ile tutabiliriz. Pointer ile tutulan nesnenin kare mi, dikdörtgen mi yoksa paralelkenar mı olduğuna bakmadan çevresini öğrenmek isteyebiliriz. Bu durumda yukarıdaki gibi pointer erişimi ile print fonksiyonunu çağırabiliriz. Çünkü dikdörtgen bir dörtgendir diyebiliriz. Ve tüm dikdörtgenlerde print fonksiyonu, işleyişi farklı da olsa vardır. Yani çok biçimlilik vardır

Bu  örnek tam anlaşılmamış olabilir.  Şu şekilde başka bir sınıf düşünelim. Okuldaki öğretmenlerden oluşan bir veritabanımız olsun. Öğretmen adında bir ana sınıfın altında fizik öğretmeni, matematik öğretmeni, stajyer öğretmen yada müdür yardımcısı gibi alt sınıflar olsun. Müdür yardımcısı aynı zamanda matematik öğretmeni de olabilir. Her hâlükârda bu sınıfların her birinde o öğretmenin programını getiren bir fonksiyon vardır. Ama bu fonksiyonların çalışma mantıkları farklı olabilir. Mesela fizik öğretmeninin ayriyeten laboratuvar dersleri olabilir. Yada müdür yardımcısının programında normal dersler haricinde şeyler olabilir. Yani bu fonksiyon çok biçimli bir fonksiyon olmalıdır.

Bu sistemde tüm öğretmenleri hangi alt sınıfta olduğuna bakmadan, öğretmen sınıfının pointer’ı ile tutarsak, yine aynı şekilde hangi alt sınıfa ait olduğunu bilmemize gerek kalmadan programını getirebiliriz.

Mesela Ahmet hocamız olsun. Ben onun ne öğretmeni olduğunu bilmiyorum. Ama öğretmen olduğunu biliyorum. Sadece ortak özelliklerin tutulduğu veritabanına bakarsam, ne öğretmeni olduğunu bilmeme gerek kalmadan ders programını öğrenebilirim.

Üst sınıf pointerı ile erişim

Şimdi kodumuza geri dönelim. Bakalım üst sınıfın pointerını kullanarak alt sınıfın fonksiyonunu çağırabilecek miyiz?

Yazılan bilgiler doğru. Kare bir dikdörtgen. Taban ve yükseklik bilgileri de doğru. Ama biz iki erişim türünde de aynı çıktıyı vermesini bekliyorduk. Yani kare sınıfının print fonksiyonu çağrılmalıydı. Ama Dikdörtgen pointer’ı kullandığımızda, veriler kare nesnesine, fonksiyon Dikdörtgen sınıfına ait oldu. Karenin özelliklerini yazdırmak için dikdörtgenin  fonksiyonunu kullandı.

Kullanılan pointer hangi tipte ise, o sınıfın üyeleri kullanlıır. Taban ve yükseklik değerleri kare sınıfına dikdörtgen sınıfının protected kısmından geldiği için bu değerlere erişmekte sıkıntı yaşamadık. Ama bizim istediğimiz fonksiyon bu değil. Talebimizi karşılamıyor. Biz kare sınıfına ait olan print fonksiyonunu kullanmak istiyoruz. Ancak şu anki mekanizmada ata sınıfın pointerı ile çocuk sınıfın fonksiyonunu kullanmamız mümkün değil. Sadece Dikdörtgen sınıfının fonksiyonlarını kullanabiliriz.Peki ya ata sınıfın pointerı ile tuttuğumuz çocuk sınıf nesnesinde, çocuk sınıfın fonksiyonlarını kullanmak istersek?

Sanal Fonksiyonlar

Bu durumda sanal fonksiyonlar devreye girer. Virtual olarak tanımlanan fonksiyonlarda, pointer önemini kaybeder. Onun yerine nesne hangi türdeyse, çok biçimli fonksiyonlar arasından onu kullanır. Tabi ki bu kelimeyi kullanabilmemiz için fonksiyonun çok biçimli olması lazım. Yani hem ata hem de çocuk sınıfta aynı imzalı fonksiyon olmalı. Fonksiyonu çağıran nesne hangi sınıfa ait ise, o sanal fonksiyonu çağırır.

Normalde sadece ata sınıfın içinde virtual olarak tanılamamız yeterlidir. Alt sınıflardaki o ada sahip fonksiyonlar otomatik olarak virtual olur. Ancak anlaması kolay olsun diye hepsinin başına virtual koymakta fayda vardır. Şimdi şekil sınıflarımızı virtual kullanarak yeniden yazalım.

İşte şimdi istediğimiz çıktıyı verdi. Kare pointerı ile dikdörtgen pointerının çıktısı aynı
Virtual olarak tanımladığımız için, fonksiyon çağırılırken pointer’ın tipine göre değil, nesnenin tipine göre çağırıldı.

 

Aslında bunu switch yapısına benzetebiliriz. Sanal bir sınıf oluşturulduğunda, derleyici o sınıfın alt sınıflarında aynı fonksiyon var mı diye bakar ve bir tablo oluşturur. Nesne tipi case olur. Hangi tip ise o çalışır.

Artık ana sınıfın pointer’ı ile istediğimiz alt sınıf nesnesini tutabiliyoruz. Ve bir fonksiyon çağıracağımız zaman da alt sınıfın fonksiyonları ile işlem yapıyor. (ana sınıfta olmayan bir üye kullanamaz. Eğer çok biçimli ise nesneninkini kullanır.)

Ancak eğer destructor virtual değilse, delete ile pointerı sildiğimizde ana sınıfın destructoru çağrılacağı için sıkıntı yaşayabiliriz. Bu yüzden dinamik bellek ile uğraşacaksak destructor mutlaka virtual olarak tanımlanmalıdır.

Sanal Sınıflar

Eğer bir sınıf tek başına anlam ifade etmiyorsa, tek amacı alt sınıflara taslak olmaksa, bu sınıfın nesnesinin oluşturulamamasını istemeyebiliriz. Böyle sınıflara sanal sınıflar (abstract class) denir. Yani nesnesi oluşturulamayan sınıflardır. En az 1 tane saf sanal fonksiyonu olmalıdır.

Sanal bir sınıfın nesnesini oluşturamayız. Sanal bir sınıftan türeyen sınıflarda, sanal sınıftan gelen tüm sanal fonksiyonlar yeniden tanımlanmalıdır. Aksi halde yeni sınıf da sanal olur. Bu sayede yeniden tanımlamamız gereken fonksiyonu tanımlamazsak derleyici hata verir. Biz de hatamızı daha hızlı fark etmiş oluruz.

Sekil adında sanal bir sınıf oluşturduk. Bu sınıfın nesnesini direk oluşturamayız. Alt sınıflarda gerekebilir diye constructor yazdık. Buradaki print fonksiyonu saf sanal, destructor sanaldır. Alt sınıflarda bu fonksiyonları yeniden tanımlamamız gerekir.

Derleyicinin bize verdiği bir yıkıcı var. Sanal sınıfları normalde tekrardan yazmamız gerekir. Ancak =default dediğimiz için derleyicinin verdiği yıkıcıyı kullanabildik.

Sonuç:

Sonuç olarak çok biçimlilik sayesinde, gelen nesnenin hangi alt sınıfa ait olduğu çalışma zamanında belli olsa da, bir takım özellikleri sıkıntısız kullanabiliriz. Bunlar ana sınıfta olan özelliklerdir. Ama sanal fonksiyonlar sayesinde özelleşmiş hallerini kullanabiliriz. Ana sınıfın pointer’ı üzerinden, virtual olarak tanımlanmış bir fonksiyon çağırırsak, pointer hangi tipte nesne tutuyorsa o nesneye ait olan fonksiyonu çağırır. Ancak sanal fonksiyonlar performans kaybı yaratacağından, zorunlu olmadıkça kullanmamakta fayda vardır.

Şimdiye kadar gördüğümüz herşeyi kullanarak son bir sınıf yazıp konuyu bitiriyoruz.

Örnek kod

Ana sınıfın pointerlarından oluşan vektör sayesinde tüm alt sınıfların elemanlarını tutabildik ve özelliklerini ekrana bastık

 

 

 

 

 

 

 

Bonus

Ana sınıf pointerı ile tutulan bir nesnemiz varsa, tekrar kendi pointerına da çevirebiliriz.
Bu sayede Ana sınıfta olmayan fonksiyonlar da tekrar kullanabiliriz.
Aynı şekilde, ana sınıfın pointerı ile tutulan bir nesnenin hangi türe ait olduğunu da bulabiliriz.

Eğer kare pointerına çevrilebiliyorsa karedir. Ama burada dikkat etmemiz gereken, kare dikdörtgenden geldiği için dikdörtgen pointerı ile de tutulabilir. Kare bir dikdörtgen olduğu için mantıken doğrudur. Ama hatalı işlem yapabiliriz.

 

    Bir cevap yazın

    E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir