Geçen hafta boyunca, nesne yönelimli programlama
dillerinde, aralarındaki farklılıklar dışında kavramsal olarak birbirlerinden
ayrılmakta zorlanan kavramlardan birisi olan soyutlamanın Java dilindeki yerini
araştırmıştım. Soyutlama ile ilgili olarak incelemelerimi tamamladıktan sonra,
sırada arayüzlerin incelenmesine vardı. C# programlama dilinden aşina olduğum
arayüzler, java dilindede yer almaktaydı. Her zaman olduğu gibi karşımda bu iki
kavram için sorulan o meşhur sorular topluluğu bulunuyordu. Neden arayüz, neden
soyutlama, hangisini kullanmalıyım, kim daha avantajlı vb...?
Nesne yönelimli dilleri geliştirenlerin bile
oldukça karmaşık bir biçimde cevaplayabilecekleri yada açıklayabilecekleri bu
ayrımı anlamak için ne kadar çaba göstersemde, sonuçlar hiç bir zaman iç açıcı
olmamıştı. Ancak en azından, arayüzlerin kullanılması ile soyutlama
arasındaki temel farklılıkları iyi bilmem gerektiği kanısındaydım. İşe öncelikle
arayüzlerin ne olduğunu tanımlamakla başlamakta fayda vardı.
Arayüzlerin en belirgin özelliği, soyut metod
tanımlamalarında olduğu gibi metod bildirimleri içermesiydi. Dolayısıyla
arayüzlerin, diğer sınıflar için yol gösterici olacak şekilde tanımalanan bir
yapı olduğunu söyleyebilirim. Bu tanım itibariyle, soyut sınıflar ile arasında
çok büyük bir benzerlik var. Her iki teknikte, kendilerini uygulayan sınıflara
rehberlik etmek üzere ortak bildirimlere izin veriyor. Soyutlamada bu, soyut
sınıflar içerisindeki soyut metodlar ve kalıtımın bir arada ele alınması ile
gerçekleştiriliyor. Arayüzlerde ise durum daha farklı. Nitekim arayüzler, soyut
sınıflarda olduğu gibi iş yapan metodlar içermiyor. Onun yerine sadece metod
bildirimlerini ve alan tanımlamalarını içeren ve kalıtım yerine implementasyonun
bir arada ele alındığı bir teknik söz konusu.
İşte bu noktada daha olayın başında, arayüzler ile
soyutlama arasındaki en temel farkı görmüş oldum. Soyutlamada soyut metod
bildirimleri haricinde iş yapan metodların var olması söz konusu iken,
arayüzlerde sadece metod bildirimleri yer almakta. Şu anda, bir arayüzün nasıl
oluşturulduğunu ve bir sınıf tarafından nasıl kullanıldığını açıkça görme
ihtiyacı hissediyorum. İşte bu isteğin karşılığında, her zaman olduğu gibi
anlamsız herhangibir iş yapmayan ama kavramı açıklayan bir kod geliştirmem
gerektiği kanısına varıyorum. Olayı sade ve basit olarak düşünmek ve kavramlar
üzerinde yoğunlaşmak şu an ihtiyacım tek şey.
interface IArayuz
{
void Yaz();
int Topla(int a,int b);
}
class Mat implements IArayuz
{
public void Yaz()
{
System.out.println("Arayuzden
uygulanan Yaz metodu");
}
public int Topla(int deger1,int deger2)
{
return deger1+deger2;
}
}
public class Uygulama
{
public static void main(String[] args)
{
Mat m=new Mat();
m.Yaz();
int topla=m.Topla(10,20);
System.out.println("10 + 20
="+topla);
}
} |
Uygulamayı derleyip çalıştırdığımda aşağıdaki
sonucu elde ettim.

Sonuçtan daha çok benim için önemli olan, arayüz
tanımlamasının yapılış şekli ve bu arayüzün bir sınıfa uygulanışıydı. Bir arayüz
interface anahtar kelimesi ile tanımlanan ve sisteme bir class dosyası olarak
kaydedilen bir yapıdır.
Diğer yandan bu arayüzü bir sınıfa uygulamak
istediğimde, implements anahtar sözcüğünü kullanmam gerekiyor.
| class Mat implements IArayuz |
Implements anahtar sözcüğü ile bu sınıfın
belirtilen arayüzü uygulayacağını ve bu nedenlede belirtilen arayüz içindeki tüm
metodları uygulaması gerektiğini söylemiş oluyoruz. C# dilinden kalma bir
alışkanlık olarak tanımladığım arayüzün bir Interface (arayüz) olduğunu daha
kolay anlayabilmek amacıyla, I harfini arayüz adının başına ekledim.
Tanımladığım Mat sınıfı içinde arayüzdeki tüm metodları override ettim. Bu bir
zorunluluk. Eğer, arayüz içinde tanımlı metodlardan override etmediğim olursa,
söz gelimi Yaz metodunu override etmessem aşağıdaki gibi bir derleme zamanı
hatası alırım.

Arayüzlerde tanımlanan metod bildirimleri ile,
soyut sınıflarda tanımlanan soyut metodlar arasındada belirgin farklılıklar
mevcut. Herşeyden önce arayüzler içindeki metod bildirimlerinde abstract anahtar
sözcüğünü kullanmıyoruz. Bununla birlikte dikkatimi çeken bir diğer önemli
noktada, arayüz metod bildirimlerinin herhangibir erişim belirleyicisini kabul
etmediği. Bu metodlar varsayılan olarak public kabul ediliyorlar ve public
dışındaki bir erişim belirleyicisi ile tanımlanamıyorlar. Bu aslında arayüzlerin
içerdiği metod bildirimlerinin, diğer sınıflara uygulandıklarında override
edilebilmelerinin bir gerekliliği olarak düşünülebilir. Uygulamamda kullandığım
arayüzdeki metodlarda başka türden erişim belirleyicileri kullanmaya
çalıştığımda derleme zamanı hataları ile karşılaştım.
interface IArayuz
{
private void Yaz();
protected int Topla(int a,int b);
} |

Gördüm ki arayüz içindeki metodlar public olmak
zorundalar. Zaten bu sebepten dolayıda varsayılan erişim belirleyicileri,
arayüzlerdeki metodlar için public olarak belirlenmiş.
Arayüzlerin kullanılmalarının en önemli
nedenlerinden biriside sınıflar üzerinde çoklu kalıtıma izin vermeleri.
Normalde, C# dilinde sınıflar arasında çoklu kalıtıma izin verilmediğini ve bu
işin arayüzler yardımıyla gerçekleştirildiğini biliyordum. Java dilinde de
durumun böyle oluşu beni çok fazla şaşırtmadı açıkçası. Kahvemin bir sonraki
yudumu çoklu kalıtım ile ilgili bir örneği geliştirmem gerektiği konusunda beyin
hücrelerimi uyarıyordu. Ah keşke birazda daha işe yarar örnekler oluşturabilmeme
yardımcı olsa şu kafein mereti. Neyse, önemli olan kavramları iyi anlayabilmek
ve nasıl uygulandıklarını teorik olarak en basit şekilde ifade edebilmek
değilmi? O halde ne duruyorum.
interface IArayuz
{
void Yaz();
int Topla(int a,int b);
}
interface IArayuz2
{
int Karesi(int a);
int Kup(int a);
}
class Mat implements IArayuz,IArayuz2
{
public void Yaz()
{
System.out.println("Arayuzden
uygulanan Yaz metodu");
}
public int Topla(int deger1,int deger2)
{
return deger1+deger2;
}
public int Karesi(int deger)
{
return deger*deger;
}
public int Kup(int deger)
{
return deger*deger*deger;
}
}
public class Uygulama
{
public static void main(String[] args)
{
Mat m=new Mat();
m.Yaz();
int topla=m.Topla(10,20);
System.out.println("10 + 20
="+topla);
System.out.println("10 un karesi =
"+m.Karesi(10));
System.out.println("10 un kubu=
"+m.Kup(10));
}
} |

Burada yaptığım, IArayuz1 ve IArayuz2
interface'lerini, Mat isimli sınıfa uygulamak. Bu şekilde, çoklu kalıtımı
sağlamış oldum. Yani bir sınıf birden fazla arayüz üyesini override ederek bu
hakkı kazanmış oldu. Yukarıdaki uygulamanın çalışma şeklini daha iyi anlamak
amacıyla da aşağıdaki gibi bir şekil geliştirdim.

Arayüzler ile ilgili bir diğer önemli konu ise,
farklı arayüzlerin aynı isimli metodlar barındırmaları sırasında ortaya
çıkabilecek sorunlar. Bunu daha iyi anlayabilmek için, soruna neden olucak bir
örnek geliştirmem gerekiyor. Öyleki, aynı isimli metod bildirimlerini içeren iki
arayüzü bir sınıfa uygulamaya çalıştığımda, bir hata mesajı almalıyım. Gelmek
istediğim nokta aslında overloading kavramının temel niteliklerinden birisi. Ama
önce örneği yazıp üzerinde düşünmek gerektiği kanısındayım.
interface IArayuzYeni
{
void Metod1(String metin);
}
interface IArayuzDiger
{
int Metod1(String a);
void Metod1(String metin, int deger);
}
class UygulayanSinif implements IArayuzYeni, IArayuzDiger
{
public void Metod1(String yazi)
{
}
public void Metod1(String yazi, int sayi)
{
}
public int Metod1(String metin)
{
return 3;
}
}
public class Uygulama2
{
} |
Burada iki arayüz içerisinde Metod1 isminde 3
farklı metod bildirimi yapılmıştır. İlk aşamada, bu arayüzlerin ikisinide
birlikte uygulayan sınıfımız için bir sorun çıkmayacağı düşünülebilir. Ancak
metodların bu şekilde aşırı yüklenmelerinde metod imzalarının önemli olduğunu
hatırlamakta fayda var. Öyleki, burada her iki arayüzde de ayrı ayrı tanımlanmış
| void Metod1(String metin);
int Metod1(String a); |
metod bildirimleri farklı arayüzlerde olasalar
dahi, aynı sınıfa uygulandıklarından aşırı yüklenmiş metodların sahip olacakları
karakteristikleri taşımaları gerkiyor. Nitekim burada bu metodların aşırı
yüklenmesini engelleyen bir olgu var ki buda metodların imzaları. Metod isimleri
ve parametreler tamamen aynı, fakat metodların geri dönüş değerinin farklı
olmasının metodun imzasında etkin rol oynamaması, aşağıdaki derleme zamanı
hatalarının oluşmasına neden olmakta.

Gelelim arayüzler ile ilgili diğer bir noktaya.
Arayüzleri oluşturduğumuzda, bunların sisteme birer class dosyası olarak
kaydedildiğini görürüz. Dolayısıyla arayüzlerin birer sınıf olduğunu
düşünebiliriz. Bununla birlikte, sınıflarda olduğu gibi arayüzleri de
birbirlerinden türetebilir ve böylece arayüzler arasında kalıtımın
gerçekleşmesini sağlayabiliriz. Örneğin,
interface ITemel
{
void Temel();
}
interface ITureyen1 extends ITemel
{
void Tureyen1();
}
interface ITureyen2 extends ITureyen1
{
void Tureyen2();
}
class deneme implements ITureyen2
{
public void Temel()
{
System.out.println("Temel arayuzun
metodunu uyguladim...");
}
public void Tureyen1()
{
System.out.println("Tureyen1
arayuzunun metodunu uyguladim...");
}
public void Tureyen2()
{
System.out.println("Tureyen2
arayuzunun metodunu uyguladim...");
}
}
public class Uygulama3
{
public static void main(String[] args)
{
deneme d=new deneme();
d.Temel();
d.Tureyen1();
d.Tureyen2();
}
} |
Bu örnekte, deneme isimli sınıf sadece ITureyen2
arayüzünü uyguluyor. Ancak bu arayüz kalıtım yolu ile, IArayuz1 interface'inden,
IArayuz1 ise, ITemel arayüzünden türetilmiş durumda. Bu nedenle, deneme
sınıfının tüm bu arayüzlerdeki metodları override etmesi gerekiyor. Olayı daha
fazla dramatize etmeden şematizme etmenin daha faydalı olacağını düşünerek
aşağıdaki umlvari grafiği hazırladım.

Kalıtımın arayüzlere nasıl uygulandığınıda
gördükten sonra aklıma soyut sınıflara arayüzlerin uygulanması nasıl olur diye
bir soru geldi. Biliyordumki arayüzleri bir sınıfa uyguladığımda, bildirilen
metodları override etmeliyim. Soyut sınıflar arayüz bildirimindeki gibi metod
bildirimleri dışında iş yapan metodlarda içerebiliyordu. Peki o halde, bir
arayüzü bir soyut sınıfa uyguladığımda, arayüzdeki metod bildirimlerini bu soyut
sınıfta override etmem gerekir miydi? Bunu anlamanın en iyi yolu arayüzü, soyut
sınıflara uygularken metodları override etmeyi ihmal etmeye çalışmaktı. Aynen
aşağıdaki kodlarda olduğu gibi.
interface ITemel
{
void Temel();
}
interface ITureyen1 extends ITemel
{
void Tureyen1();
}
interface ITureyen2 extends ITureyen1
{
void Tureyen2();
}
abstract class Soyut implements ITureyen1
{
abstract public void Metod();
} |
Yukarıdaki uygulamaya Soyut isminde bir abstract
sınıf ekledim. Bu sınıfa ITureyen1 arayüzün uyguladım. Uygulamayı derlediğimde
başarılı bir şekilde derlendiğini gördüm. Demek ki bir arayüzü bir soyut sınıfa
uygularsam, arayüzdeki metod bildirimlerini bu soyut sınıf içinde override etmem
gerekmiyormuş. Tabi elbetteki, Soyut isimli abstract sınıftan türetilen bir
sınıfın hem bu soyut sınıftaki hemde bu soyut sınıfın uyguladığı arayüzlerdeki
metodların hepsini override etmesi gerektiğini söylememede gerek yok.
Arayüzler ile ilgili en çok hoşuma giden
özelliklerden biriside iç içe geçmiş (nested) arayüz tanımlamalarının
yapılabilmesi. Yani bir arayüz tanımı başka bir arayüz tanımını ve hatta başka
arayüz tanımlamalarını bünyesinde barındırabilmekte. Tek dikkat etmem gereken
nokta, dahili arayüzlerin protected, private yada friendly erişim
belirleyicilerine sahip olamamaları. Örneğin, aşağıdaki kodlar ile, içiçie
geçmiş arayüzlerin bildirimi yapılıyor.
interface IGenel
{
interface IBilgi
{
void PersonelKunye(int ID);
}
interface IUcretlendirme
{
int MaasHesapla(int saatUcret,int
saat);
}
interface IDepartman
{
String Kod(String departmanAd);
}
}
class Personel implements IGenel.IBilgi
{
public void PersonelKunye(int PerID)
{
System.out.println("Personel NO
"+PerID);
}
}
public class Program
{
public static void main(String[] args)
{
Personel p=new Personel();
p.PersonelKunye(45855);
}
} |

Burada görüldüğü gibi IGenel arayüzü 3 adet arayüz
içermektedir. Sınıfımıza bu arayüzlerden sadece IBilgi isimli arayüzü uygulamak
için aşağıdaki söz dizimini kullandım. Dolayısıyla Personel sınıfın için, IGenel
arayüzü içindeki IBilgi arayüzünün uygulanacağını belirtmiş oldum.
| class Personel implements IGenel.IBilgi |
Burada olan şeyi şekil itibariyle ifade etmek
gerekirse aşağıdaki grafiği gösterebilirim.

Elbette IGenel arayüzünün tamamınıda
uygulayabiliriz. Bu durumda, IGenel arayüzü içerisinde yer alan tüm arayüzlerin
içerdiği metod bildirimlerinin, ilgili sınıf tarafından override edilmeleri
gerekmektedir. İç içe geçmiş arayüz tanımlamaları, birbirleriyle ilişkili, ortak
çalışamalara ve amaçlara yönelik arayüzlerin bir çatı altında toplanmasında
izlenebilecek etkili yollardan birisidir. Bu yapı aslında C# taki isim
alanlarına veya javadaki paket kavramına benzer bir sistematikle işler.
Arayüzler ile ilgili olarak kahvemizin söyleyeceği
fazla bir şey yok aslında. Arayüzler faydalıdır. Dahası günlük uygulamalarımızda
çok fazla başvuramadığımız bu sistematik yapılar, özellikle birden fazla
programcının bir arada yer aldığı büyük çaplı uygulamalarda önem kazanmakta ve
sınıfların oluşturduğu veri modellerine rehberlik yapmaktadır. Artık kafeinin
etkisi giderek azalmaya ve göz kapaklarım kapanmaya başladı. Ancak bu kahve
molası ile birlikte çok şey öğrendiğime inanıyorum. Bakalım gelecek kahve
molalarında beni ne gibi sürpriz konular bekliyor.
Burak Selim ŞENYURT
selim@bsenyurt.com