Nesneye yönelik programlamanın temellerinden olan miras kavramına bu yazımızla giriş yapacağız. Miras kavramı bir sınıfın, diğer bir sınıfın özelliklerini alabilmesine olanak veren bir mekanizmadır. Tıpkı bir çocuğun yetişmesi esnasında anne ve babasından öğrendiklerini kullanabilmesi gibi, bir java sınıfı da başka bir sınıfın methodlarını, fieldlarını kendisi için kullanabilir. Ayrıca üzerine kendine ait yeni fonksiyonaliteler ekleyebilir. Aynı zamanda bu sınıfın kendine ait bu yeni özelliklerini, bambaşka bir sınıf da kullanabilir.
Java miras kavramını IS-A mekanizmasıyla açıklar. IS-A bir ilişkisel ve İngilizce bir ifadedir. Bunu bir örnek ile pekiştirelim. Örneğin her telefon akıllı telefon değildir, ancak, her akıllı telefon bir telefondur. Dolayısı ile burada tek yönlü bir ilişki görürüz. Bunu İngilizce ile belirtirsek, “Smart phone IS-A phone” şeklinde bir ifade ile karşılaşırız. Bu örneği genişletirsek, her akıllı telefonun bir bilgisayar olduğunu ancak her bilgisayarın bir akıllı telefon olmadığını biliyoruz. Bu da bir IS-A ifadesidir. Daha geniş bir örneği değerlendirmek için bir araç modelinin IS-A diyagramına bakalım.
Bu diyagramı aşağıdan yukarıya doğru okursak 3 ayrı ifade ile karşılaşırız.
- F2004 bir Ferrari’dir
- Ferrari bir otomobildir
- Otomobil bir vasıtadır
Bu 3 ifade tahmin edebileceğiniz gibi IS-A ifadeleridir. Nihayetinde F2004’ün bir vasıta olduğu gerçeğine ulaşırız. Yani F2004 bir vasıtanın özelliklerini taşımaktadır. Yani diagram aşağıya indikçe özelleşir. Zira F2004’ün tüm özellikleri, tüm Ferrari modellerinde bulunmayabilir. Ancak tüm Ferrari modelleri Ferrari tarafından belirlenmiş standartlara sahiptir. Aynı biçimde Ferrari markalı otomobiller otomobil standartlarına ve otomobiller ise vasıta standartlarına sahiptir.
Bu diagram bazı yeni vasıtaların dahil olmasıyla daha farklı şekillere girebilirdi. Örneğin başka bir otomobil üreticisini diagrama dahil edelim.
Bu grafiği yorumladığımızda F2004 ve FW26 otomobil modellerinin bazı noktalarının ayrı ve bazı noktalarının aynı olduğunu görürüz. Şimdi bu diagramdaki her kareyi bir java sınıfı olarak düşünelim. Diyelim ki bir vasıtanın temel fonksiyonu olan hareket etme eylemini bir method olarak kodladık. Aslında bu fonksiyonun her otomobilde, her otomobil markasında ve her otomobil modelinde de olması gerekir. Eğer biz bu methodu her sınıfta ayrı ayrı kodlarsak, ileride yapılacak bir değişiklikte uygulamadaki tüm sınıfları değiştirmemiz gerekirdi. Java bu durumu miras kavramıyla çözebilmektedir.
Bu konuyu incelemek üzere çalışma alanımızda yeni bir proje oluşturuyoruz. Projemizin ismi 07 – InheritanceExtends olacak. Başlangıç için 3 ayrı sınıf oluşturarak başlayacağız. Bu sınıfların isimleri sırasıyla Vehicle, Car ve Ferrari olacak. Her sınıfın içerisine bir fonksiyonalite ekleyeceğim. Örneğin Vehicle içerisine hareketi temsilen move() methodu ve taşıt türünü temsilen type propertysini ekleyeceğim. İlk sınıfımızı paylaşıyorum.
public class Vehicle { String type; public void move() { System.out.println("Vehicle.move()"); } }
Car sınıfımıza ise vites değiştirme fonksiyonuna istinaden shift() methodunu ve yakıt türünü temsilen fuelType propertysini ekleyeceğim.
public class Car { String fuelType; public void shift() { System.out.println("Car.shift()"); } }
Gelelim Ferrari sınıfına. Bu sınıfta ise sadece Ferrari’nin güçlü motor sesini temsil edecek olan vroom() methodunu kodluyoruz.
public class Ferrari { public void vroom() { System.out.println("Ferrari.vroom()"); } }
Şu ana kadar her şey bildiğimiz şekilde yapıldı. Ancak biz biliyoruz ki bir otomobil bir vasıtadır ve hareket edebilmelidir. Aynı zamanda Ferrari de bir otomobildir ve bir vasıtanın özelliklerini taşımalıdır. Aynı zamanda bir Ferrari, vasıtaların tamamında olmayıp, otomobillerde olan (vites değişimi, yakıt tipi) gibi özelliklere de sahip olmalıdır. Yani özetle Ferrari sınıfından yaratılmış bir instance, shift() methodunu çağırabilmeli veya kara aracı olduğunu belirten type propertysine sahip olmalıdır.
Bu durumu denetlemek için TestClass sınıfımızı ve içerisinde main methodumuzu oluşturuyoruz. Bu method içerisinde Ferrari sınıfından ferrariCar isminde bir nesne yaratıyoruz ve shift() methodunu ve type fieldını kullanmaya çalışıyoruz.
Ekran görüntüsünden anlaşılacağı üzere sözdizim hatası almaktayız. Bunun sebebi mevcut şartlar altında Ferrari sınıfının içerisinde shift() methodunun ve type fieldının bulunmamasıdır. Bir çözümümüz bunları sınıfın içerisine eklemek olabilir. Ancak biz istiyoruz ki Ferrari ile birlikte Williams sınıfı kodlandığında, bu özellikler orada da varlığı sürdürsün ve bir değişiklik gerektiğinde ayrı ayrı sınıfları düzenlemek gerekmesin. O halde öncelikle Ferrari’ye otomobil (Car) olduğunu anlatalım. Bunu yapmak için kullanacağımız anahtar sözcük extends olacaktır. Ferrari sınıfımızı şu şekilde değiştiriyoruz.
public class Ferrari extends Car { public void vroom() { System.out.println("Ferrari.vroom()"); } }
Sınıfın tanımlanma şekline dikkat edelim. Sınıf isminden hemen sonra extends sözcüğü ve hemen ardından başka bir sınıfın ismi (Car) yer almaktadır. Bunu konuşma diliyle “Ferrari, Car sınıfının özelliklerini miras alır” şeklinde ifade ederiz. Sektörde ise “Ferrari, Car sınıfını extend eder” şeklinde ifade edilmektedir.
Bu değişikliğin hemen ardından main methodumuza göz atıyoruz. 8. satırdaki hatanın ortadan kaybolduğunu görüyoruz. Ancak 9. satırdaki hata devam etmekte. Hatırlarsanız type propertysi Vehicle sınıfından gelmekteydi.
Yazının başında bahsettiğim gibi java miras mekanizmasını IS-A yaklaşımıyla yönetmektedir. Bu da mirasın doğrusal olarak akması anlamına gelir. Yani iki ayrı sınıftan özellikleri almak istiyorsak bunu bir hat ile temsil etmeliyiz. Java birbirinden alakasız iki sınıftan miras yapısını desteklememektedir. Bu da şu anlama geliyor: Bir sınıf sadece bir sınıftan (IS-A) extend olabilir. O halde Ferrari’yi Vehicle sınıfından extend edemeyeceğiz. Bu durumda başvuracağımız yöntem, yukarıdaki diagramları da düşünürseniz, Car sınıfını Vehicle sınıfından extend etmektir. Bu değişikliği de şu şekilde uyguluyoruz.
public class Car extends Vehicle { String fuelType; public void shift() { System.out.println("Car.shift()"); } }
Bu değişikliği uyguladığınızda main methodumuzdaki hataların tamamen kaybolduğunu görürüz. Kodu çalıştırdığımızda sonuç da almış oluruz. İlişkinin nasıl işlediğini görmek için bir de Car sınıfından bir instance oluşturup Ferrari’ye ait vroom() methodunu kullanmaya çalışalım.
Gördüğünüz gibi bu da hata vermektedir. Çünkü “Car IS-A Ferrari” ifadesi yanlıştır. Ancak “Ferrari IS-A” car ifadesi doğrudur. Bu bağlamda Car özelleşmiş bir Vehicle, Ferrari ise özelleşmiş bir Car sınıfıdır diyebiliriz. Aynı ilişki Car ve Vehicle ile Ferrari ve Vehicle arasında da mevcuttur. “Car IS-A Vehicle”, “Ferrari IS-A Vehicle” ifadeleri doğru iken, “Vehicle IS-A Car” ve “Vehicle IS-A Ferrari” ifadeleri yanlıştır(Her zaman doğru değildir). Bu ifadeleri Williams’ın veya iş makinalarının dahil olduğu bir diagramı düşünerek kendiniz de yorumlayabilirsiniz.
Buraya kadar olan kısımdan çıkarım yapmak gerekirse, miras kavramı javada kod tekrarını önlemek ve uygulama mimarisini yönetmek için kullanılabilecek en etkili ve önemli kavramlardan birisidir. Miras kavramı tek seviyeli (B extends A), çok seviyeli (C extends B, B extends A) ve hiyerarşik (B extends A, C extends A) şeklinde gerçekleşebilir.
Şahsi fikrim miras kavramı, arayüz (interface) kavramıyla birlikte nesneye yönelik programlamanın en önemli kavramlarındandır. Bu yüzden anlaşılması oldukça önemlidir. Miras kavramına bir sonraki yazımızda da devam edeceğiz. Bir sonraki yazıda görüşmek üzere!