Miras Kavramı – II (Inheritance)

Bir önceki yazımız ile javada miras kavramına giriş yapmıştık. Bu yazımızla bu konuya devam edeceğiz. Önceki yazımızda bulunan diagramı tekrar hatırlayalım ve bu diagram üzerinden örneklerimizi genişletmeye devam edelim.

Sınıf diagramı

Bundan önce, bu diyagramın sol tarafını (Yukarıdan aşağıya doğru bunu özetlersek; Vehicle, Car ve Ferrari sınıflarını) kodlamıştık. Bununla birlikte Williams, F2004 ve FW26 sınıflarını da kodlayarak konuya devam edelim. Bu sınıfları boş olarak yaratacağız. Yani sınıf bloğunda bir kod yazmayacağız. Sınıfları şemaya uygun olarak, öncelikle kendi başınıza kodlamaya çalışın.

public class Williams extends Car {
	
}
public class FW26 extends Williams {
	
}
public class F2004 extends Ferrari {
	
}

Şimdi de bu sınıfların arasındaki ilişkilerden bahsedelim. Dikkat ederseniz diagramın aşağısı yukarıdan daha özelleşmiş sınıfları barındırmakta. Yani F2004 Ferrari sınıfının, Ferrari ise Otomobil (Car) sınıfının özelliklerini alır. Çünkü bu yönde bir IS-A ilişkisine sahiplerdir. Tersten bakarak aynı yorumu getirdiğimizde, Otomobil (Car) sınıfı özelliklerini Ferrari ve Williams sınıflarına, Williams özelliklerini FW26 sınıfına, Ferrari ise özelliklerini F2004 sınıfına aktarır. Yani bir anlamda bu sınıflar, alt sınıflar ve üst sınıflar olarak nitelendirilebilir. Sektörde bu durumlar için 2 ayrı terim kullanılmaktadır.

  • Alt sınıf: Subclass (Parent class, ebeveyn)
  • Üst sınıf: Superclass (Child class çocuk)

Yine jargon kullanarak konuşmak gerekirse; bir superclasstan faydalanıp bir subclass oluşturmak ve superclass özelliklerini inherit etmek istiyorsak, yeni sınıfı, o superclass üzerinden extend etmeliyiz. Bu cümlenin skeçlere konu olmuş “plaza dili” tanımına ne kadar uyduğunu bilsem bile, bu durumun, sektör içinde, ifade edilişini paylaşmak istedim.

İlk yazımızda üst sınıftan gelen methodların kullanımına dair konuşmuştuk. Örnek olarak ise ferrariCar isimi değişkenimizin shift methodunu Car sınıfından almasını göstermiştik. Peki bunlarla birlikte, shift methodunu inherit eden sınıflarda, bu methodu özelleştirmek istersek neler olurdu? Yani shift methodunu alt sınıflarda farklı şekillerde çalıştırmak isteseydik ne yapardık? Eğer bu özelleştirme mümkün olmasaydı, javadaki miras işleyişi oldukça sıkı tanımlara bağlı olurdu ve bizi kod tekrarından kurtaran yapı, bu özelliğini verimsiz bir hale getirirdi. Yani son derece anlamsız bir düzen ile karşılaşırdık.

O halde bir Ferrari objesinin vites değiştirmesi (shift) anında bir özelleşmeye gitmesini sağlayalım. Örneğin ekrana “Ferrari.shift()” yazdırsın. Bunun için Car sınıfımızdaki shift methodunun imzasını aynen Ferrari sınıfımıza taşıyoruz. Ancak methodumuzun gövdesini değiştirerek istediğimizi yapmasını sağlıyoruz.

public class Ferrari extends Car {
	
	public void vroom() {
		
		System.out.println("Ferrari.vroom()");
		
	}
	
	public void shift() {

		System.out.println("Ferrari.shift()");

	}
}

Sonrasında TestClass sınıfımızı şu şekilde düzenleyip çalıştırıyoruz.

public class TestClass {

	public static void main(String[] args) {

		Ferrari ferrariCar = new Ferrari();
		ferrariCar.shift();

	}

}
Çıktı:
Ferrari.shift()

Görüldüğü üzere Car sınıfımızın shift() methodu ezilmiş oldu. Bu işleme override ismi veriliyor. Detaylarına ise sonraki yazılarımızda değineceğiz. Bu durumdan çıkaracak dersimiz şöyle olacak; “Eğer bir sınıfın içerisinde bir method varsa, öncelikle o method çalıştırılır. Ancak böyle bir method yoksa, üst sınıf(lar)a bakılır. Eğer superclass(lar) bu methodu sağlamıyorsa uygulama çalışmaz”.

Ancak tahmin edersiniz ki bu durum da bazı esneklikleri de ortadan kaldırıyor. Örneğin Ferrari sınıfında bu methodu ezerek, Car sınıfında yapılan işleri yapamamış olduk. Bu da bizi bir kod tekrarına zorlayabilir. Gelin bu durumu da çözmeye çalışalım. Problemi şöyle soralım: F2004 her vites değiştirdiğinde ekrana hem “F2004.shift()” yazsın, hem de bir Ferrari vites değiştirme esnasında ne yapıyorsa, o işlemi de yapsın. Hazırsanız bu durumu çözen, super bir anahtar sözcükle tanışalım. TestClass ve F2004 sınıfımızın yeni halini aşağıya bırakıyorum.

public class F2004 extends Ferrari {
	
	public void shift() {

		super.shift();
		System.out.println("F2004.shift()");

	}
	
}
public class TestClass {

	public static void main(String[] args) {

		F2004 f2004Car = new F2004();
		f2004Car.shift();
		
	}
}
Çıktı:
Ferrari.shift()
F2004.shift()

Dikkat edeceğiniz gibi F2004 sınıfından yaratılmış instance için shift methodunu çağırdığımızda, hem atası Ferrari sınıfının yaptıklarını yapıyor, hem de kendi işlevini yerine getiriyor. Bunu sağlayan şey ise super sözcüğü. Bu sözcük tıpkı this sözcüğü gibidir. Ancak this sözcğünden farklı olarak sınıfın kendisini değil, extend olduğu atayı (superclass) temsil eder. Yani super sözcüğünü kullanarak ata(lar)daki methodlara ulaşmak ve onları kullanmak mümkündür. Eğer Ferrari sınıfında fiziksel olarak kodlanmış bir shift methodu olmasaydı bile, bu kod çalışırdı. Zira Ferrari sınıfı zaten shift methodunu Car sınıfından miras almıştı. Ferrari sınıfındaki shift methodunu silerek bu durumu test edebilirsiniz.

Şimdi işleri biraz daha karmaşıklaştıralım. Williams sınıfı içerisinde, frenlemeyi temsil eden bir brake() methodu kodlayalım. Ancak bu methodu private olarak kodlayacağız.

public class Williams extends Car {
	
	private void brake() {
		System.out.println("Williams.brake()");
	}

}

Ardından FW26 sınıfından bu methodu kullanmak için TestClass sınıfımıza doğru yol alacağız. Tahmin edeceğiniz üzere brake methodu private olduğu için compiler hata verecektir. Aşağıdaki ekran görüntüsüyle bu durumu gözlemlemleyebiliriz.

Pekala, FW26 sınıfına yeni bir brake methodu kodlarsak ne olur? Bu methodu da public yapalım.

public class FW26 extends Williams {
	
	public void brake() {
		System.out.println("FW26.brake()");
	}
	
}

Ardından TestClass sınıfındaki derleme hatasının kaybolduğunu göreceğiz. Böylece görünürdeki sorun çözülmüş oldu. Ancak FW26 için Williams sınıfının (parent class) brake() methodunu konuya dahil etmek istediğimizde tekrar benzer bir duruma düşeriz. Hata az önceki hata ile aynıdır.

Dolayısı ile buradaki durum tam olarak override etmek kavramı ile örtüşmemektedir. Bu durumun sebebini tam olarak anlamak için erişim düzenleyiciler yazımızdaki erişim tablosuna tekrar bakmanızı öneririm. Özet olarak “Private bir method, alt sınıflardan kullanılamaz” cümlesini kurabiliriz. Ancak alt sınıflardan bu methodu aynı isimle kodlayabilir durumdayız. İşte bu durumu kodlama yaparken de kontrol altında tutmak için, yani bir methodun gerçekten override edildiğini göstermek için, methodlarımızın tepesine bir annotasyon koyuyoruz.

Override annotasyonu

Dikkat ederseniz @Override annotasyonunu methodumuzun üstüne eklediğimizde, daha evvel çalışan/derlenen method hata verdi. Bunun sebebi yine üst sınıftaki methodun private olmasıdır. Ancak bu annotasyonu koymasaydık bu durumun farkında olmadan kodlamaya devam edebilirdik. Override konusuna daha sonra tekrar değineceğimiz için bu konunun üzerinde fazla durmayacağımı tekrar belirtmek istiyorum.

Javada miras kavramı üzerine söyleyebileceklerim şimdilik bu kadar. Konuların birbirine nasıl bağlandığını anlamak için lütfen önceki yazıları tekrar etmeyi aksatmayınız. Öneri olarak; özellikle sınıf yapılarına ve erişim düzenleyicilere odaklanmanızı öneririm. Bir sonraki yazımızda görüşmek üzere!

1 comment on Miras Kavramı – II (Inheritance)

Leave a Reply

Your email address will not be published. Required fields are marked *