Bu hafta boyunca, yakınlardaki ev hayvanları
dükkanında bulunan bir bukalemun'u inceledim durdum dersem, herhalde bu adam
sonunda çıldırdı diyeceksiniz. Yok henüz çıldırmadım. Aslında bu hafta boyunca
nesne yönelimli programlama dillerinin temel kavramlarından birisi olan, çok
biçimliliği inceledim. Ben çok biçimliliği bukalmenun'lara benzettiğim içinde,
sanırım bu dükkanda bir süre oyalandım. Belkide bukalemun'dan bana çok
biçimliliğin ne olduğunu söylemesini bekliyordum dersem herhalde gerçekten
çıldırdı bu adam diyeceksiniz. Yok henüz çıldırmadım :)
Nasıl ki bukalemunlar, bulundukları ortamın
şartlarına uyuyor ve çok değişik kılıklara yada biçimlere bürünebiliyorlarsa,
nesne yönelimli bir programlama dilindede bir nesne, bukalemunla aynı tarz
davranışları gösterebilmelidir. Bu nesne yönelimli programlama dillerinin
doğasında olan önemli bir özelliktir. Ancak, çok biçimlilik, kavram olarak tek
başına var olmasına rağmen, nesne yönelimli programlama dili tekniğinde,
kalıtımın bir sonucu ve bir parçası olarak karşımıza çıkmaktadır. Gerçektende,
çok biçimlilik ve kalıtım içi içe geçmiş iki önemli, nesne yönelimli programlama
kavramıdır.
Normalde sınıf kavramı, arkasından gelen kalıtım
ve bunlara bağlı olarak ortaya çıkan çok biçimlilik ile, kahveleri içtikçe java
dilinin nesne yönelimli temellerinide iyice anlamaya başlamış olduğumu
düşünüyorum. Ancak bu kavramlar genelde soyut nitelikler olduğundan tanımlar ile
anlaşılmaları gerçekten zor oluyor. İşte ben bu gibi durumlarda, her zaman basit
ve aptalca düşünmeye çalışarak, olayı en basit hali ile ele alabileceğim
örnekler geliştirmeye çalışırım. Aynen bu kahve molası için, incelemeyi
düşündüğüm çok biçimlilik kavramında olduğu gibi.
Ancak yinede ilk örneğime başlamadan önce, kafamda
basit ama etkili bir çok biçimlilik tanımı oluşturmam gerektiği düşüncesindeyim.
Bu amaçla şu tanımın çok biçimliliğe uygun olduğu kanısındayım. "Çok
biçimlilik bir nesnenin davranış şekillerinin duruma göre
değiştirilebilmesidir." Aynen, bulunduğu ortamın şartılarına göre renklerini
mükemmel bir biçimde ayarlayan sevimli bukalemun bugi gibi. Bugi bu işi
gerçekten çok iyi başarıyor. Ancak benim benzer işlevselliği bir sınıf nesne
örneğine yükleyebilmem için bilmem gereken yada sahip olmam gereken bazı temel
elementler var. Kalıtım, override ve late binding.Kalıtımı önceki kafein
molalarında öğrendiğime göre ve override etmeyi bildiğime göre doğrudan çok
biçimliliğe geçebilirim sanıyorumki.
Artık kolları sıvayıp bu kavrama girişimi
sağlayacak ilk örneğimi geliştirmemin zamanı geldi. Bir yudum kahvenin ardından,
aşağıdaki kodları oluşturdum. Burada kalıtım ilişkisi olan sınıf tanımlamaları
ve temel sınıfta tanımlanıp türeyen sınıflarda override edilen basit metodlar
mevcut. Amacım çok biçimliliği temel sınıf türünden bir nesneye uygulamak.
Uygulamak ama bu nasıl olucak ve ne işe yarıyacak? Asıl cevaplanması gereken
sorular işte bunlar. Öncelikle tabiki örneği yazıp başarılı bir şekilde derlemem
gerekiyor.
class TemelSinif
{
public void Yaz()
{
System.out.println("Ben TEMEL
sinifim");
}
}
class Tureyen_1 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_1
sinifiyim");
}
}
class Tureyen_2 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_2
sinifiyim");
}
}
class Tureyen_3 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_3
sinifiyim");
}
}
public class Program
{
public static void Yaz(TemelSinif t)
{
t.Yaz();
}
public static void main(String[] args)
{
Tureyen_1 t1=new Tureyen_1();
Tureyen_2 t2=new Tureyen_2();
Tureyen_3 t3=new Tureyen_3();
Yaz(t1);
Yaz(t2);
Yaz(t3);
}
} |
Kodu Program.java ismli ile kaydedip derlediğim ve
çalıştırdığımda aşağıdaki sonucu elde ettim.

Çalışma şeklini görünce "Oha Falan Oldum Yani"
dedim kendimce. Evet bir kod yazmıştım ancak çok biçimlilik bu kodun
neresindeydi. Dahası sevimli bukalemun bugi nerelerdeydi; O kadar iyi kamufle
olmuştuki koda ilk baktığımda onu görememiştim. Öncelikle kodun içerisinde yer
alan sınıfların hiyerarşisine yakından bakmak gerekiyordu. Bu amaçla şu meşhur
uml diagramları tekniğini kullanmaya çalıştım ve sınıflar arası ilişkiyi
aşağıdaki şekilde olduğu gibi kağıda döktüm.

TemelSinif isimli base class'tan türeyen üç adet
derived class'ım vardı elimde. Ayrıca Temel sınıfta tanımlanan Yaz isimli metod
türeyen sınıflar içerisinde iptal edilerek yeniden yazılıyordu (override).
Buraya kadar her şey gayet açıktı. Bu noktada benim için önemli olan kilit nokta
Program sınıfı içerisindeki Yaz metoduydu.
public static void Yaz(TemelSinif t)
{
t.Yaz();
} |
Bu metodun ilginç olan yanı, TemelSinif türünden
bir nesne örneğini parametre olarak alması ve aldığı nesneye ait Yaz metodunu
çağırmasıydı. İşte şu kafaları karıştıran çok biçimlilik buydu ve bizim bugi'de
çok güzel şekil değiştirerek t nesnesi oluvermişti. Program sınıfının main
metodundaki kod satırları çok biçimliliğin kullanılmasının bir yolunu
göstermekteydi.
public static void main(String[] args)
{
Tureyen_1 t1=new Tureyen_1();
Tureyen_2 t2=new Tureyen_2();
Tureyen_3 t3=new Tureyen_3();
Yaz(t1);
Yaz(t2);
Yaz(t3);
} |
Main metodunda türeyen sınıflardan 3 farklı nesne
tanımlanmıştı. Her bir nesnenin Yaz metodunu ayrı ayrı çağırabilirdim. Ancak
bunun yerine, tüm bu sınıfların türediği, temel sınıf türünden bir nesne
örneğini parametre alan bir metodu çağırmayı ve buradaki türeyen sınıf
nesnelerini bu metoda göndermeyi tercih etmiştim. Peki olan olay tam olarak
neydi? Örneğin Yaz(t1) ile ne gibi işlemler gerçekleştiriliyordu?
Yaz(t1), Program sınıfım içerisindeki static Yaz
metodunu çağırıyor ve buna t1 nesnesini parametre olarak gönderiyordu. Yaz
metodunun parametresi ise t1 nesnesinin türetildiği TemelSinif türündendi.
Bingo. İşte çok biçimliliğin en temel noktasını yakalamıştım. Türeyen nesne ne
olursa olsun, Yaz metodu sadece tek bir nesne kullanıyor ve bu tek nesne
üzerinden, kendisine atanan türeyen sınıfa ait üyeleri çalıştırıyordu.
Dolayısıyla t1 nesnesi Yaz metoduna aktarıldığında, TemelSinif türünden t
nesnesine atanmış ve doğal olarak t.Yaz() kod satırı ile t1'in Yaz metodu
çağırılmıştı. Bunu sağlayan elbetteki TemelSinif'in türeyen sınıf üyelerine
erişebiliyor olmasıydı.
Çok biçimlilik burada Program sınıfının static Yaz
metodunun gerçekleştirdiği işlevsellikti. Hangi sınıfın Yaz metodunun
çalıştırılacağı burada belli oluyor dolayısıyla minik bugi t nesne örneği,
şartlara uygun hareket ediyordu. İşin suyunu çıkartıp programın çalışma biçimini
grafiğe dökmeye karar verdim. Sonuç olarak ikinci bir kahve fincanı doldurmam
gerekmişti ama kafamdaki şekillerde çok biçimsel anlamlar kazanmaya başlamıştı.

Şekilde görüldüğü gibi Program sınıfındaki static
Yaz metodu, çalışma zamanında çağırıldığında, TemelSinif türünden bir t nesnesi
tanımlanıyor ve bu t nesnesine, Yaz metodunun çağırılmasında gönderilen türeyen
sınıf nesnesi atanıyor. Bunun sonucu olarak bu t nesnesi, gelen sınıfın heap
bölgesindeki adresini referans etmeye başlıyor. Doğal olarak, t.Yaz metodu
ilede,parametre olarak gelen türeyen sınıf nesnesinin Yaz metodu çağırılmış
oluyor.
Burada aslında önemli bir nesne yönelimli
programlama tekniği kavramıda mevcut. Late Binding(Geç Bağlama). Yukarıdaki
şeklin bana söylediği en önemli şey olayların çalışma zamanında gerçekleşiyor
olması. Bunun ifade ettiği şey ise oldukça önemli. Nitekim, program çalışmaya
başladıktan sonra, biz static Yaz metodunu çağırdığımızda, bu metod içinden
hangi türeyen sınıfın Yaz metodunun çağırılacağı henüz bilinmemektedir. Bu
ancak, türeyen sınıf nesnesi metoda parametre olarak atanıp, TemelSinif türünden
t nesnesine atandığında belli olucaktır. Yani hangi Yaz metodunun
çağırılıacağına çalışma zamanında karar verilmektedir. İşte bu olay, geç bağlama
(late binding) olarak adlandırılmakta olup, çok biçimliliğin bir parçası olarak,
nesne yönelimli programlama dilinin temel yapı taşlarından birisi olarak
karşımıza çıkmaktadır.
Çok biçimliliği böylece tanımaya başlamış oldum.
Peki bu kavram benim nerede işime yarayabilirdi. Bu cevaplanması gerçekten çok
zor bir soru. Bence esas olan çok biçimliliğin bize sağlamış olduğu faydayı
anlamaya çalışmak. Ancak yukarıdaki örneği normal programlama teknikleri ile
yapmaya çalışsaydık acaba nasıl bir kod yazımı olurdu. Yani çok biçimlilik
uygulamadan. Bu noktada uzun zamandır nesne yönelimli diller ile uğraşan birisi
olarak bu çeşit bir kodu yazmak benim için oldukça zor oldu. Sonunda aşağıdaki
gibi bir sonuç ortaya çıktı. Şekilse açıdan en önemlisi çalışma şekli açısından
oldukça kötü bir örnek.
class TemelSinif
{
public void Yaz()
{
System.out.println("Ben TEMEL
sinifim");
}
}
class Tureyen_1 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_1
sinifiyim");
}
}
class Tureyen_2 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_2
sinifiyim");
}
}
class Tureyen_3 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_3
sinifiyim");
}
}
public class Program4
{
public static void Yaz(Object obj)
{
if(obj instanceof Tureyen_1)
{
Tureyen_1
t1=(Tureyen_1)obj;
t1.Yaz();
}
else if(obj instanceof Tureyen_2)
{
Tureyen_2
t2=(Tureyen_2)obj;
t2.Yaz();
}
else if(obj instanceof Tureyen_3)
{
Tureyen_3
t3=(Tureyen_3)obj;
t3.Yaz();
}
else if(obj instanceof TemelSinif)
{
TemelSinif
t=(TemelSinif)obj;
t.Yaz();
}
}
public static void main(String[] args)
{
Tureyen_1 t1=new Tureyen_1();
Tureyen_2 t2=new Tureyen_2();
Tureyen_3 t3=new Tureyen_3();
Yaz(t1);
Yaz(t2);
Yaz(t3);
}
} |
Örnekte çok biçimliliğin olmadığını varsayarak
hareket ettim. Bu durumda, ortak static Yaz metoduna gönderdiğim nesnelerin
tipleri belli olmadığı için, Object sınıfından bir nesne örneğini parametre
olarak belirtmem gerekti. Daha sonra bu metoda gönderilen nesnelerin hangi sınıf
tipinden olduklarını öğrenmek amacıyla instanceof anahtar kelimesinin
kullanıldığı if bloklarını işin içine kattım. Bu if bloklarında nesnenin hangi
tipten olduğuna baklıyor ve daha sonra bu nesne için yeni bir örnek new
operatörü ile oluşturuluyor ve daha sonra bu yeni nesne örneği üzerinden gerekli
Yaz metodu çalıştırılıyordu. Oysaki aynı örneği çok biçimlilik yolu ile ne kadar
basit ve anlamlı yapmıştık. Zaten kim söylemiş ise doğru söylemiş. "Basit ama
aptalca düşünmeye devam et."
Dolayısıyla sonuç olarak çok biçimliliğin faydası
hakkında şunu söyleyebilirim artık; "Çok biçimlilik sayesinde, çalışma zamanında
nesne davranışlarına çeşitlilik katabilmekteyiz."
Çok biçimliliği başka nasıl kullanabiliriz diye
düşünmeye başladığım sırada aklıma fabrika çalışanları geliverdi. Bir fabrikanın
çalışanlarına ilişkin bilgileri tutan temel bir sınıf olduğunu düşündüm. Bu
sıfıntan türeyen ve işçiler arasındaki kategorileri birer sınıf olarak temsil
eden türemiş sınıflar olucaktı. Temel sınıfta tanımlanmış, saat ücreti üzerinden
maaş hesabı yapan bir metodu, türeyen sınıflarda yeniden yazdım yani override
ettim. Her türeyen sınıfın kendisine göre bir saat ücreti olacağından, maaş
hesaplamalarıda farklı olucaktı. Programın içerisinde, tüm fabrika işçilerinin,
temel sınıftan bir dizi içerisinde tutulduğunu düşündüm, bu diziye çok
biçimliliği uygulayarak, tek bir nesne üzerinden, hangi türeyen sınıfa ait
metodun çalıştırılacağınının çalışma zamanında karara bağlanmasını sağlamaya
çalıştım. O halde ne duruyorum. Bu örneği geliştirip çok biçimlilikle iligli
bilgileri pekiştirmeye karar verdim ve kahvemden bir fırt çekip uygulamayı
yazmaya başladım.
class Isciler
{
public int hesapla()
{
return -1;
}
}
class Kidemli_Isciler extends Isciler
{
int saatUcreti=100;
int saat;
public Kidemli_Isciler(int s)
{
saat=s;
}
public int hesapla()
{
return saatUcreti*saat;
}
}
class Vasifli_Isciler extends Isciler
{
int saatUcreti=80;
int saat;
public Vasifli_Isciler(int s)
{
saat=s;
}
public int hesapla()
{
return saatUcreti*saat;
}
}
public class Program2
{
public static void main(String[] args)
{
Isciler[] isciler=new Isciler[3];
isciler[0]=new Kidemli_Isciler(10);
isciler[1]=new Vasifli_Isciler(5);
isciler[2]=new Kidemli_Isciler(20);
for(int i=0;i<isciler.length;i++)
{
int
ucret=isciler[i].hesapla();
System.out.println(ucret);
}
}
} |

Çok biçimlilik ile ilgili gözden kaçan bir
ayrıntıyıda çalışmalarımı yaparken farkettim. Çok biçimliliğin uygulanması
aslında programların çalışma performansı üzerinde azaltıcı bir etki
yaratmaktaydı. Dolayısıyla geç bağlama söz konusu olduğunda uygulamanın verimi
düşüyordu. Bu amaçla ilk yazdığım işe yaramayan ancak çok biçimliliğin ne
olduğunu gösteren minik program parçasının dahada aptallaştırılmış sürümünü göz
önüne aldım.
class TemelSinif
{
public void Yaz()
{
System.out.println("Ben TEMEL
sinifim");
}
}
class Tureyen_1 extends TemelSinif
{
public void Yaz()
{
System.out.println("Ben Tureyen_1
sinifiyim");
}
}
public class Program3
{
public static void Yaz(TemelSinif t)
{
t.Yaz();
}
public static void main(String[] args)
{
Tureyen_1 t1=new Tureyen_1();
TemelSinif t=new TemelSinif();
Yaz(t1);
Yaz(t);
}
} |
Burada verimi düşeren neydi acaba? Biraz düşününce
aslında bunun sebebi anlaşılabiliyordu. Olay Yaz(t1) çağırımında kendisini
gösteriyordu. Yaz(t1) ile, static Yaz metodu çağırlıyor TemelSinif nesnesi
türünden t sınıfına ait Yaz metodu çalıştırılıyordu. İşte çalışma zamanında java
sanal makinesi, bu noktada geç bağlama olup olmadığını kontrol etmekteydi.
Burada TemelSinif t nesne örneğine Türeyen_1 sınıfından bir nesne atanmıştı.
Dolayısıyla burada geç bağlamadan söz edebilirdik. Bunu gören JVM bu işlemin
ardından türeyen sınıfın Yaz metodunun override edilip edilmediğine bakıcak ve
ondan sonra bu metodu çağırıcaktı. İşte bu zincir çalışma verimini düşüren bir
etken olarakta karşımıza çıkıyordu.
Çok biçimlilik (Polimorphism) kavramı nesne
yönelimli programlama kavramlarının önemli bir elemanı olarak artık kafamda
iyice şekillenmişti. Artık Java'da ilerlemeye devam edebilirdim. Ama önce
alışverişe çıkıp kahve ve süt tozu stoklarımı yenilemem gerekiyor.
Burak Selim ŞENYURT
selim@bsenyurt.com