Skip to content
Ağu 28 / E. Basri Kahveci

Java’da Dahili Sınıflar (Inner Classes)

1.1 sürümünde Java’ya eklenen dahili sınıflar, bir çok açıdan bize avantaj sağlıyorlar. Dahili sınıfların ne olduğudan kısaca bahsedip, sağladığı avantajlar üzerine birkaç şey söylemek istiyorum.

Bir sınıfın içinde tanımlanan sınıflara dahili sınıf adı veriliyor. Normal bir sınıf bir paketin üyesi iken, dahili bir sınıf ise içinde tanımlandığı sınıfın bir üyesi oluyor.

Dahili sınıfları 4’e ayırıyoruz: Statik üye sınıflar, statik olmayan üye sınıflar, yerel sınıflar ve isimsiz sınıflar.

Statik üye sınıf, tanımlandığı bir sınıfın statik bir üyesi oluyor (çok açıklayıcı oldu değil mi :) ). Statik üye sınıflar, aynı statik metodlar gibi, tanımlandığı sınıfın diğer statik üyelerine erişim yapabiliyorken, statik olmayan alanlarına doğrudan erişim yapamıyor.

Statik olmayan üye sınıf da içinde tanımlandığı sınıfın bir üyesi iken, statik üye sınıfın aksine tanımlandığı sınıfın private da dahil tüm üyelerine doğrudan erişebiliyor (hatta this referansını farklı bir şekilde kullanarak tanımlandığı sınıfın nesnesine doğrudan erişiyor).

Yerel sınıflar herhangi bir kod bloğunun içinde tanımlanıp aynı yerel değişken mantığında olduğu gibi yalnızca o kod bloğunda etkin oluyorlar.

İsimsiz sınıf ise isim verilmeden yerel olarak tanımlanan sınıflara deniyor. Bu tanım biraz garip oldu biraz daha açık söyleyecek olursak herhangi bir kod bloğunda bir arayüzü gerçekleştiren bir sınıf tanımını isimsiz olarak yapabiliyoruz. Kodla göstereyim. Şöyle bir arayüzümüz olsun.

interface A {
	public void doSomething();
}

Herhangi bir kod bloğunun içinde:

//...
A a = new A() {
	Public void doSomething() {
		System.out.println(“I am doing something”);
	}};
//...

diyerek, A arayüzünü gerçekleştiren isimsiz bir sınıftan bir nesne yaratıp a nesnesine atamış olduk. Bu tarz kodlar GUI yazarken event handler’larda falan çok kullanılıyor görmüş olmanız kuvvetle muhtemel.

Güzel bir şey daha, normal sınıflar yalnızca public veya package-private olabiliyorken, dahili sınıflar public, protected, private, package-private yani tüm erişim belirteçlerine sahip olabiliyor.

Dahili sınıfların ne tür avantajları var?

Dahili sınıflar bize birçok açıdan avantaj sağlıyor. Dahili sınıflar sarmalamayı (encapsulation) artırıyor, mantıksal bir bütünlük içerisinde olan sınıfları bir arada tutmamızı sağlıyor, her ne kadar dahili sınıflara alışık olmayan bir programcıya garip görünse de aslında kodu daha kolay okunabilir ve yönetilebilir yapıyor. Örneğin bir sınıf üzerinde yardımcı işlevler üstlenen küçük küçük sınıfları bir paketin içine koymak yerine o sınıfa dahili sınıf olarak eklemek daha yerinde bir yaklaşım.

Şöyle bir örnek vereyim: Diyelim ki kendimiz bir Collection gerçekleştirimi yazdık. Bu basit bir bağlı liste olabileceği gibi karmaşık bir yapıda öğeleri tutan herhangi bir amaca yönelik bir şey de olabilir. Nasıl bir yapıda olduğunun bizim için önemi yok, bizim için önemli olan onun bir Collection olması.

Collection ne iş yapar? İçine nesne atarız, herhangi bir nesne içerisinde bulunuyor mu diye bakarız, nesneleri içinden çıkarırız falan filan. Yani Collection’ların görevi -en basit tanımla- bir şeyleri tutmak.

Yazdığımız Collection’u kullanacak istemcinin bizden ekstra birkaç isteği olsun. Aklıma ilk gelen şey (Java’daki standart Collection’ların Iterator sağlama özelliğinden dolayı) Iterator. Yani istemci bizim yapımızın içindeki nesneleri tek tek dolaşabilmek istiyor. Bu isteği nasıl karşılayacağımızı biraz düşünelim.

Şimdi, dikkatle düşünecek olursak istemcinin bizim hakkımızda tek bildiği şey bizim bir Collection olduğumuz. Sıfımızın adı neymiş, gerçekleştirimimizi nasıl yapmışız, nesneleri içerde diziyle mi tutmuşuz, bunların hiçbiriyle ilgilenmiyor. Bu yüzden adam bizim yapımız üzerinde iterasyon yapmak istediğinde “al kardeşim ben bu Collection’u diziyle yaptım sana veriyorum bu diziyi al bunun üstünde istediğin gibi gez dersek” encapsulation olayına ihanet etmiş oluruz. Hadi bunu geçtik, Collection’umuzun bir sonraki sürümünde diziden vazgeçtik de nesneleri başka bir veri yapısında tutmaya karar verdik. İstemciye “kusura bakma kardeşim geçen sefer diziyle çalışıyodum, şimdi iş değişti iterasyon için artık bunu kullanacaksın” diyemeyiz. Dersek muhtemelen o adam bizim son istemcimiz falan olur.

Daha iyi bir çözüm düşünelim

Collection arayüzününde tanımlı metodların yanına 3-5 metod daha ekleyerek işi çözebiliriz örneğin. Öyle yaptık diyelim. nextElement(), currentElement(), previousElement() gibi metodlarla istemciye iterasyon imkanı sunduk. Bunu birkaç açıdan inceleyelim. Öncelikle istemci bizi Collection arayüzü aracılığıyla biliyordu. Bu metodlar ise o arayüze dahil değil. Yani istemcinin bizim yapımız üzerinde iterasyon yapabilmesi için bizim sınıfımızı arayüz üzerinden değil de doğrudan kendi referansımızla kullanması lazım. Yani, bizim sınıfımıza bağımlı olması lazım. Bağımlılık… Nesneye yönelik tasarımda malesef “Bağımlılık bütün kötülüklerin anasıdır” prensibi geçerlidir. İstemci bizimle Collection üzerinden iletişim kurarken şöyle bir esnekliği vardı. Bir gün gelip ben artık başka bir sınıf ile çalışmak istiyorum dediğinde Collection referansının ilklendirildiği yer dışında kodunun hiçbir yerini değiştirmesi gerekmiyordu. Çünkü aradaki arayüz bizimle istemci arasında bir kontrattı. İstemci o kontrata uyan başka herhangi bir sınıfı bizim sınıfımızın yerine koyup kullanabilirdi. Ama artık bizim sınıfımıza doğrudan bağımlı. Yarın başka bir sınıfı kullanmak istediğinde kodu açıp birçok şeyi tek tek elle değiştirmek zorunda kalacak.

Şunu diyebilirsiniz. Bu sadece Collection arayüzü için geçerli. Yani Collection arayüzü Java ile hazır olarak gelen bir arayüz. Biz kendi yapımız için bir arayüz sağlayıp, iterasyon metodlarını da onun içine koyup, istemciye “al bizimle bu arayüz üzerinden iletişim kur” diyebilirdik. Evet, bu belki biraz daha kabul edilebilir bir yaklaşım oluyor ama hala yetersiz. En basitinden bazı durumlarda arayüzü tanımlama hakkına sahip olmayabiliriz. Biz tanımlamış olsak da önceden istemciye açtığımız bir arayüzü değiştirmek pek hoş sonuçlar doğurmayabilir.

Dahili sınıfları anlatmaya niyetliydim ama plansız yazdığımdan olay biraz farklı yerlere kaydı. Neyse güzel şeylerden bahsettiğimi düşündüğüm için bunu çok önemsemiyorum. En sonunda bi yerden dahili sınıflara mevzuyu bağlarız 

Iterasyon işini Collection sınıfının içinde yapıp, kullanıcıya metodlarla iterasyon imkanını sunduğumuzu düşünelim. İstemcinin işi görüldü mü, görüldü. Ama yine de durumu tasarım açısından inceleyelim.

İki saattir Collection diyip duruyorum. Bir bir Collection gerçekleştirdik. Biz Collection arayüzünü gerçekleştiren bir sınıf yazdık. Bizim sınıfımızın asıl görevi bir Collection olmak. Bizim sınıfımızın sorumluluğu, nesneleri tutmak. Biz gittik sınıfımıza bir de iterasyon görevi verdik. Collection sadece bir Collection’dır. Yirminci kez söylüyorum. Collection’ın görevi nesneleri tutmaktır. Collection sadece nesneleri tutar. Iterasyon işi onun görevi değildir. Ama biz Iterasyon sorumluluğunu da ona verdik. Yarın istemci bizden, ben bu sınıf üzerindeki verilerde şu şu şu işlemleri de gerçekleştirmek istiyorum dediğinde onu da o sınıfın içinde gerçekleştirirsek sınıfa bir tane daha sorumluluk kazandırmış oluyoruz.

Bu şekilde bir yaklaşımla nesneye yönelik tasarımın temel prensiplerinden, “Single Responsibility” prensibini hiçe saymakla kalmayıp, resmen prensibin anasını ağlattık. Single Responsibility prensibi bize der ki, bir nesnesin yalnızca bir sorumluluğu vardır. Bizim sınıfımızın ise daha şu anda 2 sorumluluğu var. İlerleyen zamanda aynı mantık ile gidersek sınıfımız yeni sorumluluklar edinecek gibi görünüyor.

Peki bir sınıfın çok sorumluluğu olsa ne olur?

Bir sınıf birden fazla sorumluluk üstlendiğinde büyür ve karmaşıklaşır. Collection sınıfımızda olduğu gibi kendi işiyle alakalı işler dışında şeyler de yapmaya başladığında, anlaşılması, tekrar kullanılabilmesi, yönetilmesi zor bir hal alır. Ayrıca şöyle bir şey daha var. Her bir sorumluluk, kodda değişim için bir sebeptir. Birden fazla sorumluluk bir arada olduğunda kod değişikliklerden etkilenen bir hal alır.

Saydığım sebeplerin, şu anki gerçekleştiririmizden vazgeçmemiz için yeterli bir sebep olduğu kanaatindeyim. Daha farklı bir şeyler denemeliyiz. Merak etmeyin, dahili sınıflara geldik sayılır 

Single Responsibility ne diyordu, bir sınıf sadece tek bir sorumluluğu yerine getirmelidir. Biz Iterasyon sorumluluğunu Collection sınıfımıza dahil etmiştik. Peki bu sorumluluğu Collection’ımızdan alıp ayrı bir sınıfa versek nasıl olur? Ben söyleyeyim, tam süper olur :D Bir sınıfa 2 sorumluluğu da vermektense, her biri tek bir sorumluluğu yerine getiren 2 sınıf gerçekleştirmek çok daha iyi bir çözümdür. Yani iterasyon işlemini yapan bir sınıf yazacağız ve istemci bizim yapımızda iterasyon yapmak istediğinde bu sınıfı kullanacak. Kulağa hoş geliyor değil mi?
Şimdi işleri tekrar bir gözden geçirelim. Artık Collection sınıfımızı gerçek bir Collection sınıfı olarak tasarladık. Onun görevi nesneleri tutmak. İstemci yapımız üzerinde Iterasyon yapmak istemişti, onu da ayrı bir sınıfla istemciye sunduk. (Pek tabii ki iterasyon sınıfımız bizim Collection sınıfımız üzerinde iterasyon yapacağından bizim sınıfımıza bağımlılık duyuyor.) İstemci ileride bizden başka bir şey daha istediğinde, örneğin Collection’a attığı nesnelerden belli özelliğe sahip olanları elde etmek istediğinde, bu özelliği doğrudan Collection sınıfımıza yerleştirmek yerine, ayrı bir sınıfta gerçekleştirebiliriz. Bu şekilde Collection’umuzu hala bir Connection olarak muhafaza etmiş olduk. Ayrıca ne oldu, önceden yazdığımız Collection sınıfını çok değiştirmeden yapımıza yeni yeni özellikler katabileceğimiz bir tasarıma doğru ilerledik. Bu güzel bir şey.

Peki şimdi dahili sınıf olayına gelelim. Iterasyon sorumluluğunu yerine getirecek sınıfı düşündüğümüzü varsayalım. Bir kere Collection sınıfımızın arkasına koyduğumuz gerçekleştirimi hiçbir şekilde kullanıcıya göstermek istemiyoruz. Fakat Iterasyon sınıfımızın iterasyon işlemini gerçekleştirebilmesi için bizim Collection gerçekleştirimimizi bilmesi gerekli. Neticede bir dizi ile gerçekleştirim yaptıysak dizinin indeks değerleriyle tek tek iterasyonu gerçekleştireceğiz, ama bir bağlı liste ile gerçekleştirim yaptıysak bağlı listenin öğeleri üzerinde ilerlemek suretiyle iterasyon gerçekleştireceğiz. İşte bunu statik olmayan dahili sınıf ile kolayca yapabiliriz. Ne demiştik? Statik olmayan dahili sınıf kendisini kapsayan sınıfın tüm üyelerine erişim yapabilir. Yani biz Collection gerçekleştirimimize Iterasyon amacıyla yazdığımız statik olmayan dahili sınıfın metodlarından doğrudan erişebiliriz. Sayfa sayfa şeyi sadece bunu söylemek için yazmış gibi oldum ama bence güzel şeylerden bahsettim.

Iterasyon işlemini dahili sınıfla gerçekleştirmemizin bir diğer güzel yanı da şu: Mantıksal açıdan Iterasyon sınıfımız bizim asıl Collection sınıfımız için bir yardımcı sınıf. Bu sınıfı aynı paketin içine koymak yerine sınıfın içine koymak bana göre daha iyi bir kod yapısı sunuyor.

Dahili sınıfların ayrı bir kullanım yönü olarak şunu da söyleyebilirim. Dahili üye sınıflar ve üye sınıflar callback’ler tanımlamak için güzel bir yöntem sunuyor. Bunu GUI’lerde çok sık görüyoruz. Örneğin farenin sol tuşu tıklandığında gerçekleştirilecek olan işlemi fareyi temsil eden nesneye MouseClickHandler arayüzünü gerçekleştiren bir (isimsiz veya üye) dahili sınıf vererek belirliyoruz.

Başka bir örnek olması açısından şunu da aktarayım. Şu aralar meşgale olsun diye bir görev kuyruğu gerçekleştirimi yapmaya giriştim (onun kodunu da tamamlayınca vermeyi düşünüyorum). Gerçekleştirim şöyle: Görev kuyruğu nesnesine dışarıdan bir arayüzü gerçekleştirilmiş nesneler ekleniyor. Nesnenin içerisinde çalışan iş parçacıkları da kuyruğa eklenen nesneleri kuyruktan tek tek alıp, Runnable arayüzünü gerçekleştiren dahili bır üye sınıf ile sarmalayıp, görevleri işletiyor. Aslında gayet basit bir işlevi var. Dahili sınıfın metodu içerisinde dışarıdan gelen sınıfın metodunu çağırmadan önce ve sonra ekstradan kontrol, kayıt tutma gibi işlemleri de yapabilmiş oluyorum. Dahili sınıfı private tanımladığımdan istemcinin gerçekleştirimi nasıl yaptığımdan haberi olmuyor, ayrıca dahili sınıfım bana nesnel tasarım açısından avantaj sağladığından ortaya güzel bir tasarım ve gerçekleştirim çıkıyor.

Dahili sınıflar üzerine yakın zamanda bir kod paylaşmayı düşünüyorum.

Farkındayım çok dağınık bir yazı oldu, umarım birileri buraya kadar okumayı başarabilir :D

Bu yazıyı sosyal ağlarda paylaş:
  • Digg
  • del.icio.us
  • Facebook
  • Google Bookmarks
  • blogmarks
  • MySpace
  • Slashdot
  • StumbleUpon
  • Technorati
  • LinkedIn
Leave a Comment