Wednesday, July 17, 2013

Java'da Soyut Sınıf ve Arayüz Kullanımı

Sınıf, nesne, kalıtım ve çok şekillilik nesneye dayalı programlamanın temel bileşenleridir. Modellemeyi ve gerçeklemeyi bu yapıları kullanarak sağlıyoruz. Java programlama dili de nesneye dayalı bir programlama dilidir. Söz dizimsel özellikleri C++ programlama dilininkine benzer. İşleyişte ise bazı temel farklar bulunur. Bu farklardan biri C++ ve Java'nın çoklu kalıtımı destekleme biçimleriyle ilgilidir. C++, bir sınıfın, birden fazla sınıftan, doğrudan türetilmesine izin verir. Bu çoklu kalıtım olarak adlandırılır. Ancak bu özelliğin sorunlu bazı yönleri bulunmaktadır. Bu sorunlardan birini, çoklu kalıtım uygulamasının yer aldığı aşağıda verilen kod üzerinde görmeye çalışalım. Aşağıdaki kod derlenmek istenirse, derleyici D sınıfının getX() metodu için hata iletisi verir ve derleme başarısız olur.

class A {

  protected:

     int x;

  public:

     virtual int getX(){

        return x;
      }
};
class B : public A {
  public:
      int getX(){
        return 2 * x;
      }
};
class C : public A {
  public:
      int getX(){
        return 3 *x;
      }
};
class D : public B , public C {
  public:
      int getX(){
        return 4 * x;
      }
};
Yukarıdaki çoklu kalıtım uygulamasında, türetme yapılan sınıfların ortak bir sınıftan türetilmiş olmaları durumunda, bu ortak sınıflardan gelen veriler, kalıtım yoluyla birden fazla kere tekrarlı olarak türetilmiş sınıfa aktarılır. Bu nedenle türetilmiş sınıfta, tekrarlı elemana erişimin olduğu yerlerde, derleyici belirsizlik hatası verecektir. Derleyici B üzerinden gelen x'e mi yoksa C sınıfı üzerinden gelen x'e mi erişilmek istenildiğine karar veremez. Yukarıdaki tasarımda A sınıfında tanımlanan x hem B hem de C üzerinde tekrarlı olarak D sınıfına aktarılır. C++'da bu problemin sezgisel olmayan sanal kalıtım olarak adlandırılan bir çözümü bulunmaktadır. Burada B ve C sınıfını kodlayan programcılar A sınıfından türetme yaparken sanal kalıtım uygulaması yaparlar:
class B : virtual public A {
  public:
      int getX(){
         return 2 * x;
      }
};
class C : virtual public A {
  public:
      int getX(){
        return 3 *x;
      }
};
Şimdi D sınıfına, A sınıfından kalıtımla sadece tek bir x aktarılır. Bu çözümün doğal olmayan bir yönü bulunmaktadır. Eğer B ve C sınıfları farklı programcılar tarafından kodlanıyorsa bu çözümün öngörülemez olduğu açıktır. Üstelik programcılardan birinin sanal kalıtımı uygulaması ancak diğer programcının uygulamaması durumunda önerilen çözüm çalışmaz. Java'da bir sınıfın sadece tek bir sınıfı türetebiliyor olmasının temel nedeni bu türden durumlar yaratmamaktır. Böylelikle Java'da yukarıda C++ için verilen örnekteki gibi bir durum tekli kalıtım kullanılması nedeni ile asla oluşmaz.
Fiziksel uzayda (problem uzayında) bazen doğal olarak çoklu kalıtım durumları ile karşılaşıyoruz. Bu gibi durumlarda Java'nın sağladığı çözüm arayüz (=interface) kullanmaktır. Az sonra Java ara yüzlerinin özelliklerini detaylı olarak çalışacağız. Ancak kısaca söylemek gerekirse, Java'da bir sınıf sadece tek bir sınıfından türetilebilirken (extends), birden fazla arayüzü gerçekleyebilir (=implements). Üstelik Java arayüzleri arasında çoklu kalıtım vardır: bir arayüz birden fazla arayüzden türetilebilir. Arayüz kullanıldığında, çoklu kalıtımın C++'daki gibi bir yan etkisi ile karşılaşılmamasının nedeni, arayüzlerde öznitelik tanımlanmasına izin verilmemesidir. Soyut sınıflar ile arayüz arasında önemli bir ilişki vardır. Az sonra bu ilişkinin özeliklerini çalışacağız. Ancak önce soyut sınıfları çalışalım.
Çok şekillilik için aşağıdaki üç özelliğin sağlanması gerekir:
  1. Aralarında kalıtım ilişkisi olan sınıflar,
  2. Bu sınıflarda yer alan çok şekilli metotlar,
  3. Bu sınıflar arasında en temel sınıfından tanımlanmış bir referans.
Bu referansı kullanarak yapılan tüm çağrılar, yürütme zamanında bağlanır ve dinamik bağlama olarak adlandırılır. Çok şekillilik için gerekli olan birinci koşul çoğu zaman doğal olarak oluşur ancak bazen nadir de olsa oluşmaz. Örneğin Bird, Airplane, Ufo (Unidentified Funny Objects) ve Superman sınıfları arasında kalıtım ilişkisi yoktur. Bird, Airplane değildir, Superman, Ufo değildir. Ancak bu dört sınıfta da ortak bir davranış vardır: fly(). fly() metodu çok şekilli bir metottur. Ancak çok şekilli bir metodun varlığı, sınıflar arasında kalıtım ilişkisi kurulamadığı için çok şekillilik için yeterli olmaz. Sadece çok şekilliliği kurabilmek için gerçek dünyada olmayan bir sınıf uydurulur. Bu sınıf Flyable olsun. Şimdi problem uzayımızdaki tüm sınıflar (Bird, Airplane, Ufo ve Superman) bu uydurulan sınıftan türetilir. Bu sınıfın gerçek dünyada olmayan, sırf çok şekillilik için kafamızdan "uydurduğumuz" bir sınıf olduğunu betimlemek üzere sınıfı soyut sınıf olarak adlandırıyoruz. Java'da soyut sınıf tanımlaması abstract anahtar kelimesi kullanılarak yapılır. Soyut bir sınıfın içinde en az bir tane soyut bir metot olması beklenir. Soyut metot abstract anahtar kelimesi ile tanımlanır. Örneğimizdeki Flyable soyut sınıfı aşağıdaki gibi tanımlanır:
public abstract class Flyable {
   public abstract void fly();
}
Sanal sınıflar gerçek dünyada var olmadıkları için çözüm uzayında da var olmamaları gerekir. Bu nedenle soyut sınıftan nesne yaratılmasına izin verilmez. Soyut sınıftan türetilen sınıflarda, soyut metotlara mutlaka işlev yüklemek gerekir:
public class Bird extends Flyable {
     @Override
     public void fly() {
       System.out.println("Bird is flying now...");
     }
}
public class Airplane extends Flyable {
     @Override
     public void fly() {
       System.out.println("Airplane is flying now...");
     }
}
public class Ufo extends Flyable {
     @Override
     public void fly() {
       System.out.println("Ufo is flying now...");
     }
}
public class Superman extends Flyable {
     @Override
     public void fly() {
       System.out.println("Superman is flying now...");
     }
}
Soyut sınıftan nesne yaratamıyor olsak da soyut bir sınıfın verisi ve kurucu metodu bulunabilir. Soyut bir sınıf başka bir soyut sınıftan türetilebilir. Bu durumda soyut sınıfın soyut metotlarına işlev yüklemesi gerekmez. Soyut bir sınıf bir arayüzü gerçekleyecek ise onun metotlarına işlev yüklemek zorunda değildir. Bu durumda arayüzün metotları soyut sınıfın soyut metotlarına dönüşür. Soyut sınıfın somut metotları olabilir. Somut metot gövdesini kıvırcık parantez kullanarak verdiğimiz, gerçek dünyada örneği ile karşılaşabildiğimiz ve bunun bir sonucu olarak da modelleyebildiğimiz metotlardır.
Java arayüzleri özel bir tür soyut sınıftır. Arayüzlerin verisi, kurucusu, somut metodu olamaz:
           Soyut Sınıf = Arayüz + Kurucu + Veri + Somut Metod
    Arayüz = Soyut Sınıf  - Kurucu - Veri - Somut Metod
Tüm metotları soyut olmak zorundadır. Üstelik bu soyut metotlar public tanımlı olmak zorundadır. Arayüz metotları hem public hem de abstract tanımlı olmak zorunda oldukları için tanımlanırken bunun açık olarak belirtilmesi gerekmez. Örtük olarak public ve abstract olduğu kabul edilir. Yukarıdaki Flyable soyut sınıfının verisi ve somut metodu olmadığı için arayüz olarak da gerçeklenebilir. Arayüz tanımını interface anahtar kelimesini kullanarak veriyoruz:
public abstract interface Flyable {
   public abstract void fly();
}
Bu durumda, Bird, Airplane, Ufo ve Superman sınıflarını Flyable arayüzünü kullanarak implements anahtar kelimesi ile tanımlıyoruz:
public class Bird implements Flyable {
     @Override
     public void fly(){
       System.out.println("Bird is flying now...");
     }
}
public class Airplane implements Flyable {
     @Override
     public void fly(){
       System.out.println("Airplane is flying now...");
     }
}
public class Ufo implements Flyable {
     @Override
     public void fly(){
       System.out.println("Ufo is flying now...");
     }
}
public class Superman implements Flyable {
     @Override
     public void fly(){
       System.out.println("Superman is flying now...");
     }
}
Daha gelişmiş bir örneği NetBeans projesi olarak bu bağlantıdan indirebilirsiniz. Örnek modellemeyi daha rahat takip edebilmeniz için oluşturduğum UML sınıf diyagramını aşağıda bulabilirsiniz:


No comments:

Post a Comment